上一篇开始,我们就提到了一个概念,并且进行了初步的运用,这个概念就是实体。

KBE中的实体是一个很重要的概念,可以说,有了实体就有了一切。

我们首先接着上一章的内容,来看Account.def对应的实体定义。

  1. <root>
  2. <Properties>
  3. <characters>
  4. <Type> AVATAR_INFOS_LIST </Type>
  5. <Flags> BASE </Flags>
  6. <Default> </Default>
  7. <Persistent> true </Persistent>
  8. </characters>
  9.  
  10. <lastSelCharacter>
  11. <Type> DBID </Type>
  12. <Flags> BASE_AND_CLIENT </Flags>
  13. <Default> 0 </Default>
  14. <Persistent> true </Persistent>
  15. </lastSelCharacter>
  16.  
  17. <activeCharacter>
  18. <Type> MAILBOX </Type>
  19. <Flags> BASE </Flags>
  20. </activeCharacter>
  21.  
  22. <lastClientIpAddr>
  23. <Type> UINT32 </Type>
  24. <Flags> BASE </Flags>
  25. <Default> 0 </Default>
  26. </lastClientIpAddr>
  27. </Properties>
  28.  
  29. <ClientMethods>
  30. <onReqAvatarList>
  31. <!-- http://www.kbengine.org/cn/docs/programming/entitydef.html
  32. Utype参数是可选的
  33. 属性的自定义协议ID,如果客户端不使用KBE配套的SDK来开发,客户端需要开发跟KBE对接的协议,
  34. 开发者可以定义属性的ID便于识别,c++协议层使用一个uint16来描述,如果不定义ID则引擎会使用
  35. 自身规则所生成的协议ID, 这个ID必须所有def文件中唯一
  36. 这里只是一个演示,demo客户端并没有用到
  37. -->
  38. <Utype> 10003 </Utype>
  39. <Arg> AVATAR_INFOS_LIST </Arg>
  40. </onReqAvatarList>
  41.  
  42. <onCreateAvatarResult>
  43. <Utype> 10005 </Utype>
  44. <Arg> UINT8 </Arg>
  45. <Arg> AVATAR_INFOS </Arg>
  46. </onCreateAvatarResult>
  47.  
  48. <onRemoveAvatar>
  49. <Arg> DBID </Arg>
  50. </onRemoveAvatar>
  51. </ClientMethods>
  52.  
  53. <BaseMethods>
  54. <reqAvatarList>
  55. <Exposed/>
  56. <Utype> 10001 </Utype>
  57. </reqAvatarList>
  58.  
  59. <reqCreateAvatar>
  60. <Exposed/>
  61. <Utype> 10002 </Utype>
  62. <Arg> UINT8 </Arg> <!-- roleType -->
  63. <Arg> UNICODE </Arg> <!-- name -->
  64. </reqCreateAvatar>
  65.  
  66. <selectAvatarGame>
  67. <Exposed/>
  68. <Utype> 10004 </Utype>
  69. <Arg> DBID </Arg> <!-- dbid -->
  70. </selectAvatarGame>
  71.  
  72. <reqRemoveAvatar>
  73. <Exposed/>
  74. <Arg> UNICODE </Arg> <!-- name -->
  75. </reqRemoveAvatar>
  76.  
  77. <reqRemoveAvatarDBID>
  78. <Exposed/>
  79. <Arg> DBID </Arg> <!-- dbid -->
  80. </reqRemoveAvatarDBID>
  81. </BaseMethods>
  82.  
  83. <CellMethods>
  84. </CellMethods>
  85.  
  86. </root>

在看这个文件的时候,初学者往往一脸懵逼,常见的疑问有以下几种:

1.实体文件是怎么定义的?

2.实体是怎样存在的?

3.实体中用到的类型是怎样的?

4.实体中Flag的定义是什么?

5.实体的节点之间的RPC是如何进行的?

我们来一个个的解答这些问题

实体文件的定义

大量内容拷贝自官方文档:http://kbengine.org/cn/docs/programming/entitydef.html

什么时候需要定义实体:

  1. 需要进行数据存储。
  2. 能够方便的远程访问。
  3. 需要引擎管理和监控, 例如: AOITrap、等等。
  4. 当灾难发生后服务端可以自动进行灾难的恢复。

什么时候需要定义实体的属性:

  1. 需要进行数据存储。
  2. 实体被迁移后数据仍然有效(仅cellapp会迁移实体,比如跳转场景)。
  3. 当灾难发生后服务端可以自动进行灾难的恢复。

什么时候需要定义实体的方法:

  1. 能够方便的远程访问。

一份标准的实体文件格式:

  1. <root>
  2. // 该实体的父类def
  3. // 这个标签只在Entity.def中有效,如果本身就是一个接口def则该标签被忽略
  4. <Parent> Avatar </Parent>
  5.  
  6. // 易变属性同步控制
  7. <Volatile>
  8. // 这样设置则总是同步到客户端
  9. <position/>
  10.  
  11. // 没有显式的设置则总是同步到客户端
  12. <!-- <yaw/> -->
  13.  
  14. // 设置为0则不同步到客户端
  15. <pitch> 0 </pitch>
  16.  
  17. // 距离10米及以内同步到客户端
  18. <roll> 10 </roll>
  19. </Volatile>
  20.  
  21. // 注册接口def,类似于C#中的接口
  22. // 这个标签只在Entity.def中有效,如果本身就是一个接口def则该标签被忽略
  23. <Implements>
  24. // 所有的接口def必须写在entity_defs/interfaces中
  25. <Interface> GameObject </Interface>
  26. </Implements>
  27.  
  28. <Properties>
  29. // 属性名称
  30. <accountName>
  31. // 属性类型
  32. <Type> UNICODE </Type>
  33.  
  34. // (可选)
  35. // 属性的自定义协议ID,如果客户端不使用kbe配套的SDK来开发,客户端需要开发跟kbe对接的协议,
  36. // 开发者可以定义属性的ID便于识别,c++协议层使用一个uint16来描述,如果不定义ID则引擎会使用
  37. // 自身规则所生成的协议ID, 这个ID必须所有def文件中唯一
  38. <Utype> 1000 </Utype>
  39.  
  40. // 属性的作用域 (参考下方:属性作用域章节)
  41. <Flags> BASE </Flags>
  42.  
  43. // (可选)
  44. // 是否存储到数据库
  45. <Persistent> true </Persistent>
  46.  
  47. // (可选)
  48. // 存储到数据库中的最大长度
  49. <DatabaseLength> 100 </DatabaseLength>
  50.  
  51. // (可选, 不清楚最好不要设置)
  52. // 默认值
  53. <Default> kbengine </Default>
  54.  
  55. // (可选)
  56. // 数据库索引, 支持UNIQUE与INDEX
  57. <Index> UNIQUE </Index>
  58. </accountName>
  59.  
  60. ...
  61. ...
  62. </Properties>
  63.  
  64. <ClientMethods>
  65. // 客户端暴露的远程方法名称
  66. <onReqAvatarList>
  67. // 远程方法的参数
  68. <Arg> AVATAR_INFOS_LIST </Arg>
  69.  
  70. // (可选)
  71. // 方法的自定义协议ID,如果客户端不使用kbe配套的SDK来开发,客户端需要开发跟kbe对接的协议,
  72. // 开发者可以定义属性的ID便于识别,c++协议层使用一个uint16来描述,如果不定义ID则引擎会使用
  73. // 自身规则所生成的协议ID, 这个ID必须所有def文件中唯一
  74. <Utype> 1001 </Utype>
  75. </onReqAvatarList>
  76.  
  77. ...
  78. ...
  79. </ClientMethods>
  80.  
  81. <BaseMethods>
  82. // Baseapp暴露的远程方法名称
  83. <reqAvatarList>
  84. // (可选)
  85. // 定义了此标记则允许客户端调用,否则仅服务端内部暴露
  86. <Exposed/>
  87.  
  88. // (可选)
  89. // 方法的自定义协议ID,如果客户端不使用kbe配套的SDK来开发,客户端需要开发跟kbe对接的协议,
  90. // 开发者可以定义属性的ID便于识别,c++协议层使用一个uint16来描述,如果不定义ID则引擎会使用
  91. // 自身规则所生成的协议ID, 这个ID必须所有def文件中唯一
  92. <Utype> 1002 </Utype>
  93. </reqAvatarList>
  94.  
  95. ...
  96. ...
  97. </BaseMethods>
  98.  
  99. <CellMethods>
  100. // Cellapp暴露的远程方法名称
  101. <hello>
  102. // (可选)
  103. // 定义了此标记则允许客户端调用,否则仅服务端内部暴露
  104. <Exposed/>
  105.  
  106. // (可选)
  107. // 方法的自定义协议ID,如果客户端不使用kbe配套的SDK来开发,客户端需要开发跟kbe对接的协议,
  108. // 开发者可以定义属性的ID便于识别,c++协议层使用一个uint16来描述,如果不定义ID则引擎会使用
  109. // 自身规则所生成的协议ID, 这个ID必须所有def文件中唯一
  110. <Utype> 1003 </Utype>
  111. </hello>
  112. </CellMethods>
  113.  
  114. </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

脚本自带的类型有以下几种:

  1. [Name] [Size]
  2. UINT8 1
  3. UINT16 2
  4. UINT32 4
  5. UINT64 8
  6. INT8 1
  7. INT16 2
  8. INT32 4
  9. INT64 8
  10. FLOAT 4
  11. DOUBLE 8
  12. VECTOR2 12
  13. VECTOR3 16
  14. VECTOR4 20
  15. STRING N
  16. UNICODE N
  17. PYTHON N
  18. PY_DICT N
  19. PY_TUPLE N
  20. PY_LIST N
  21. MAILBOX N
  22. BLOB N

UINT32很容易理解,DBID我们点开entity_defs/alias.xml,也很容易找到对应的定义,其实是一个UNIT64类型的整数。

Mailbox是什么呢,API文档里是这么解释的

脚本层与实体远程交互的常规手段(其他参考:allClientsotherClientsclientEntity)。
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找到这个类型

  1. <AVATAR_INFOS_LIST> FIXED_DICT
  2. <implementedBy>AVATAR_INFOS.avatar_info_list_inst</implementedBy>
  3. <Properties>
  4. <values>
  5. <Type> ARRAY <of> AVATAR_INFOS </of> </Type>
  6. </values>
  7. </Properties>
  8. </AVATAR_INFOS_LIST>

我们打开user_type/AVATAR_INFOS.py,更详细定义如下

  1. # -*- coding: utf-8 -*-
  2. import KBEngine
  3. import GlobalConst
  4. from KBEDebug import *
  5.  
  6. class TAvatarInfos(list):
  7. """
  8. """
  9. def __init__(self):
  10. """
  11. """
  12. list.__init__(self)
  13.  
  14. def asDict(self):
  15. data = {
  16. "dbid" : self[0],
  17. "name" : self[1],
  18. "roleType" : self[2],
  19. "level" : self[3],
  20. "data" : self[4],
  21. }
  22.  
  23. return data
  24.  
  25. def createFromDict(self, dictData):
  26. self.extend([dictData["dbid"], dictData["name"], dictData["roleType"], dictData["level"], dictData["data"]])
  27. return self
  28.  
  29. class AVATAR_INFOS_PICKLER:
  30. def __init__(self):
  31. pass
  32.  
  33. def createObjFromDict(self, dct):
  34. return TAvatarInfos().createFromDict(dct)
  35.  
  36. def getDictFromObj(self, obj):
  37. return obj.asDict()
  38.  
  39. def isSameType(self, obj):
  40. return isinstance(obj, TAvatarInfos)
  41.  
  42. avatar_info_inst = AVATAR_INFOS_PICKLER()
  43.  
  44. class TAvatarInfosList(dict):
  45. """
  46. """
  47. def __init__(self):
  48. """
  49. """
  50. dict.__init__(self)
  51.  
  52. def asDict(self):
  53. datas = []
  54. dct = {"values" : datas}
  55.  
  56. for key, val in self.items():
  57. datas.append(val)
  58.  
  59. return dct
  60.  
  61. def createFromDict(self, dictData):
  62. for data in dictData["values"]:
  63. self[data[0]] = data
  64. return self
  65.  
  66. class AVATAR_INFOS_LIST_PICKLER:
  67. def __init__(self):
  68. pass
  69.  
  70. def createObjFromDict(self, dct):
  71. return TAvatarInfosList().createFromDict(dct)
  72.  
  73. def getDictFromObj(self, obj):
  74. return obj.asDict()
  75.  
  76. def isSameType(self, obj):
  77. return isinstance(obj, TAvatarInfosList)
  78.  
  79. avatar_info_list_inst = AVATAR_INFOS_LIST_PICKLER()

因为kbe的C++部分只支持自定义FIXED_DICT类型,所以所有自定义类型在进行网络传输和存储操作的时候其实都是FIXED_DICT类型,用户需要自己实现自定义类型的序列化getDictFromObj和反序列化函数createObjFromDict函数。

所以AVATAR_INFOS_LIST类型其实是一个dbid为主键的字典类型,存储着玩家角色列表。

实体中Flag的定义是什么

flag定义其实是属性的作用域,官方API给了一个列表来说明属性的作用域

  1. [类型] [ClientEntity] [BaseEntity] [CellEntity]
  2. BASE - S -
  3. BASE_AND_CLIENT C S -
  4. CELL_PRIVATE - - S
  5. CELL_PUBLIC - - SC
  6. CELL_PUBLIC_AND_OWN C - SC
  7. ALL_CLIENTS C(All Clients) - SC
  8. OWN_CLIENT C - S
  9. 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

  1. namespace KBEngine
  2. {
  3. using UnityEngine;
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7.  
  8. /*
  9. 实体的Mailbox
  10. 关于Mailbox请参考API手册中对它的描述
  11. https://github.com/kbengine/kbengine/tree/master/docs/api
  12. */
  13. public class Mailbox
  14. {
  15. // Mailbox的类别
  16. public enum MAILBOX_TYPE
  17. {
  18. MAILBOX_TYPE_CELL = , // CELL_MAILBOX
  19. MAILBOX_TYPE_BASE = // BASE_MAILBOX
  20. }
  21.  
  22. public Int32 id = ;
  23. public string className = "";
  24. public MAILBOX_TYPE type = MAILBOX_TYPE.MAILBOX_TYPE_CELL;
  25.  
  26. private NetworkInterface networkInterface_;
  27.  
  28. public Bundle bundle = null;
  29.  
  30. public Mailbox()
  31. {
  32. networkInterface_ = KBEngineApp.app.networkInterface();
  33. }
  34.  
  35. public virtual void __init__()
  36. {
  37. }
  38.  
  39. bool isBase()
  40. {
  41. return type == MAILBOX_TYPE.MAILBOX_TYPE_BASE;
  42. }
  43.  
  44. bool isCell()
  45. {
  46. return type == MAILBOX_TYPE.MAILBOX_TYPE_CELL;
  47. }
  48.  
  49. /*
  50. 创建新的mail
  51. */
  52. public Bundle newMail()
  53. {
  54. if(bundle == null)
  55. bundle = Bundle.createObject();
  56.  
  57. if(type == Mailbox.MAILBOX_TYPE.MAILBOX_TYPE_CELL)
  58. bundle.newMessage(Message.messages["Baseapp_onRemoteCallCellMethodFromClient"]);
  59. else
  60. bundle.newMessage(Message.messages["Base_onRemoteMethodCall"]);
  61.  
  62. bundle.writeInt32(this.id);
  63.  
  64. return bundle;
  65. }
  66.  
  67. /*
  68. 向服务端发送这个mail
  69. */
  70. public void postMail(Bundle inbundle)
  71. {
  72. if(inbundle == null)
  73. inbundle = bundle;
  74.  
  75. inbundle.send(networkInterface_);
  76.  
  77. if(inbundle == bundle)
  78. bundle = null;
  79. }
  80. }
  81.  
  82. }

可以看到,所谓的cellcall和basecall只是发了两个不同的消息到后端而已,分别是Baseapp_onRemoteCallCellMethodFromClient和Base_onRemoteMethodCall,我们到后端找Base_onRemoteMethodCall

  1. //-------------------------------------------------------------------------------------
  2. void Base::onRemoteMethodCall(Network::Channel* pChannel, MemoryStream& s)
  3. {
  4. SCOPED_PROFILE(SCRIPTCALL_PROFILE);
  5.  
  6. if(isDestroyed())
  7. {
  8. ERROR_MSG(fmt::format("{}::onRemoteMethodCall: {} is destroyed!\n",
  9. scriptName(), id()));
  10.  
  11. s.done();
  12. return;
  13. }
  14.  
  15. ENTITY_METHOD_UID utype = ;
  16. s >> utype;
  17.  
  18. MethodDescription* pMethodDescription = pScriptModule_->findBaseMethodDescription(utype);
  19. if(pMethodDescription == NULL)
  20. {
  21. ERROR_MSG(fmt::format("{2}::onRemoteMethodCall: can't found method. utype={0}, methodName=unknown, callerID:{1}.\n",
  22. utype, id_, this->scriptName()));
  23.  
  24. s.done();
  25. return;
  26. }
  27.  
  28. // 如果是外部通道调用则判断来源性
  29. if (pChannel->isExternal())
  30. {
  31. ENTITY_ID srcEntityID = pChannel->proxyID();
  32. if (srcEntityID <= || srcEntityID != this->id())
  33. {
  34. WARNING_MSG(fmt::format("{2}::onRemoteMethodCall({3}): srcEntityID:{0} != thisEntityID:{1}.\n",
  35. srcEntityID, this->id(), this->scriptName(), pMethodDescription->getName()));
  36.  
  37. s.done();
  38. return;
  39. }
  40.  
  41. if(!pMethodDescription->isExposed())
  42. {
  43. ERROR_MSG(fmt::format("{2}::onRemoteMethodCall: {0} not is exposed, call is illegal! srcEntityID:{1}.\n",
  44. pMethodDescription->getName(), srcEntityID, this->scriptName()));
  45.  
  46. s.done();
  47. return;
  48. }
  49. }
  50.  
  51. if(g_debugEntity)
  52. {
  53. DEBUG_MSG(fmt::format("{3}::onRemoteMethodCall: {0}, {3}::{1}(utype={2}).\n",
  54. id_, (pMethodDescription ? pMethodDescription->getName() : "unknown"), utype, this->scriptName()));
  55. }
  56.  
  57. pMethodDescription->currCallerID(this->id());
  58. PyObject* pyFunc = PyObject_GetAttrString(this, const_cast<char*>
  59. (pMethodDescription->getName()));
  60.  
  61. if(pMethodDescription != NULL)
  62. {
  63. if(pMethodDescription->getArgSize() == )
  64. {
  65. pMethodDescription->call(pyFunc, NULL);
  66. }
  67. else
  68. {
  69. PyObject* pyargs = pMethodDescription->createFromStream(&s);
  70. if(pyargs)
  71. {
  72. pMethodDescription->call(pyFunc, pyargs);
  73. Py_XDECREF(pyargs);
  74. }
  75. else
  76. {
  77. SCRIPT_ERROR_CHECK();
  78. }
  79. }
  80. }
  81.  
  82. Py_XDECREF(pyFunc);
  83. }

到了这个类会调用具体的脚本对应的方法,来进行处理

到此为止,实体这个概念的全部内容讲解完成,我们接着上一章的内容讲解account相关方法

请求角色列表:

客户端

  1. public override void __init__()
  2. {
  3. Event.fireOut("onLoginSuccessfully", new object[]{KBEngineApp.app.entity_uuid, id, this});
  4. baseCall("reqAvatarList", new object[]);
  5. }

服务器

  1. def reqAvatarList(self):
  2. """
  3. exposed.
  4. 客户端请求查询角色列表
  5. """
  6. DEBUG_MSG("Account[%i].reqAvatarList: size=%i." % (self.id, len(self.characters)))
  7. self.client.onReqAvatarList(self.characters)

服务器的处理很简单,直接把实体内部的characters这个属性返回回去了

可以看到,建立了mailbox通讯后,服务器的脚本逻辑是非常的简单。

我们来看下其他功能

创建角色:

客户端

  1. public void reqCreateAvatar(Byte roleType, string name)
  2. {
  3. Dbg.DEBUG_MSG("Account::reqCreateAvatar: roleType=" + roleType);
  4. baseCall("reqCreateAvatar", new object[]{roleType, name, "LSM_TEST_19870508"});
  5. }

服务器端

  1. def reqCreateAvatar(self, roleType, name):
  2. """
  3. exposed.
  4. 客户端请求创建一个角色
  5. """
  6. avatarinfo = TAvatarInfos()
  7. avatarinfo.extend([0, "", 0, 0, TAvatarData().createFromDict({"param1" : 0, "param2" :b''})])
  8.  
  9. """
  10. if name in all_avatar_names:
  11. retcode = 2
  12. self.client.onCreateAvatarResult(retcode, avatarinfo)
  13. return
  14. """
  15.  
  16. if len(self.characters) >= 3:
  17. DEBUG_MSG("Account[%i].reqCreateAvatar:%s. character=%s.\n" % (self.id, name, self.characters))
  18. self.client.onCreateAvatarResult(3, avatarinfo)
  19. return
  20.  
  21. """ 根据前端类别给出出生点
  22. Reference: http://www.kbengine.org/docs/programming/clientsdkprogramming.html, client types
  23. UNKNOWN_CLIENT_COMPONENT_TYPE = 0,
  24. CLIENT_TYPE_MOBILE = 1, // 手机类
  25. CLIENT_TYPE_WIN = 2, // pc, 一般都是exe客户端
  26. CLIENT_TYPE_LINUX = 3 // Linux Application program
  27. CLIENT_TYPE_MAC = 4 // Mac Application program
  28. CLIENT_TYPE_BROWSER = 5, // web应用, html5,flash
  29. CLIENT_TYPE_BOTS = 6, // bots
  30. CLIENT_TYPE_MINI = 7, // 微型客户端
  31. """
  32. spaceUType = GlobalConst.g_demoMaps.get(self.getClientDatas(), 1)
  33.  
  34. # 如果是机器人登陆,随机扔进一个场景
  35. if self.getClientType() == 6:
  36. while True:
  37. spaceName = random.choice(list(GlobalConst.g_demoMaps.keys()))
  38. if len(spaceName) > 0:
  39. spaceUType = GlobalConst.g_demoMaps[spaceName]
  40. break
  41.  
  42. spaceData = d_spaces.datas.get(spaceUType)
  43.  
  44. props = {
  45. "name" : name,
  46. "roleType" : roleType,
  47. "level" : 1,
  48. "spaceUType" : spaceUType,
  49. "direction" : (0, 0, d_avatar_inittab.datas[roleType]["spawnYaw"]),
  50. "position" : spaceData.get("spawnPos", (0,0,0))
  51. }
  52.  
  53. avatar = KBEngine.createBaseLocally('Avatar', props)
  54. if avatar:
  55. avatar.writeToDB(self._onAvatarSaved)
  56.  
  57. DEBUG_MSG("Account[%i].reqCreateAvatar:%s. spaceUType=%i, spawnPos=%s.\n" % (self.id, name, avatar.cellData["spaceUType"], spaceData.get("spawnPos", (0,0,0))))
  58.  
  59. def _onAvatarSaved(self, success, avatar):
  60. """
  61. 新建角色写入数据库回调
  62. """
  63. INFO_MSG('Account::_onAvatarSaved:(%i) create avatar state: %i, %s, %i' % (self.id, success, avatar.cellData["name"], avatar.databaseID))
  64.  
  65. # 如果此时账号已经销毁, 角色已经无法被记录则我们清除这个角色
  66. if self.isDestroyed:
  67. if avatar:
  68. avatar.destroy(True)
  69.  
  70. return
  71.  
  72. avatarinfo = TAvatarInfos()
  73. avatarinfo.extend([0, "", 0, 0, TAvatarData().createFromDict({"param1" : 0, "param2" :b''})])
  74.  
  75. if success:
  76. info = TAvatarInfos()
  77. info.extend([avatar.databaseID, avatar.cellData["name"], avatar.roleType, 1, TAvatarData().createFromDict({"param1" : 1, "param2" :b''})])
  78. self.characters[avatar.databaseID] = info
  79. avatarinfo[0] = avatar.databaseID
  80. avatarinfo[1] = avatar.cellData["name"]
  81. avatarinfo[2] = avatar.roleType
  82. avatarinfo[3] = 1
  83. self.writeToDB()
  84.  
  85. avatar.destroy()
  86.  
  87. if self.client:
  88. self.client.onCreateAvatarResult(0, avatarinfo)

代码很简单,先创建角色,创建角色成功后再更新账号的角色列表。

这里之所以要销毁avatar,是因为avatar创建以后不一定立即使用。

进入游戏:

客户端:

  1. public void selectAvatarGame(UInt64 dbid)
  2. {
  3. Dbg.DEBUG_MSG("Account::selectAvatarGame: dbid=" + dbid);
  4. baseCall("selectAvatarGame", new object[]{dbid});
  5. }

服务器端:

  1. def selectAvatarGame(self, dbid):
  2. """
  3. exposed.
  4. 客户端选择某个角色进行游戏
  5. """
  6. DEBUG_MSG("Account[%i].selectAvatarGame:%i. self.activeAvatar=%s" % (self.id, dbid, self.activeAvatar))
  7. # 注意:使用giveClientTo的entity必须是当前baseapp上的entity
  8. if self.activeAvatar is None:
  9. if dbid in self.characters:
  10. self.lastSelCharacter = dbid
  11. # 由于需要从数据库加载角色,因此是一个异步过程,加载成功或者失败会调用__onAvatarCreated接口
  12. # 当角色创建好之后,account会调用giveClientTo将客户端控制权(可理解为网络连接与某个实体的绑定)切换到Avatar身上,
  13. # 之后客户端各种输入输出都通过服务器上这个Avatar来代理,任何proxy实体获得控制权都会调用onEntitiesEnabled
  14. # Avatar继承了Teleport,Teleport.onEntitiesEnabled会将玩家创建在具体的场景中
  15. KBEngine.createBaseFromDBID("Avatar", dbid, self.__onAvatarCreated)
  16. else:
  17. ERROR_MSG("Account[%i]::selectAvatarGame: not found dbid(%i)" % (self.id, dbid))
  18. else:
  19. self.giveClientTo(self.activeAvatar)
  20.  
  21. def __onAvatarCreated(self, baseRef, dbid, wasActive):
  22. """
  23. 选择角色进入游戏时被调用
  24. """
  25. if wasActive:
  26. ERROR_MSG("Account::__onAvatarCreated:(%i): this character is in world now!" % (self.id))
  27. return
  28. if baseRef is None:
  29. ERROR_MSG("Account::__onAvatarCreated:(%i): the character you wanted to created is not exist!" % (self.id))
  30. return
  31.  
  32. avatar = KBEngine.entities.get(baseRef.id)
  33. if avatar is None:
  34. ERROR_MSG("Account::__onAvatarCreated:(%i): when character was created, it died as well!" % (self.id))
  35. return
  36.  
  37. if self.isDestroyed:
  38. ERROR_MSG("Account::__onAvatarCreated:(%i): i dead, will the destroy of Avatar!" % (self.id))
  39. avatar.destroy()
  40. return
  41.  
  42. info = self.characters[dbid]
  43. avatar.cellData["modelID"] = d_avatar_inittab.datas[info[2]]["modelID"]
  44. avatar.cellData["modelScale"] = d_avatar_inittab.datas[info[2]]["modelScale"]
  45. avatar.cellData["moveSpeed"] = d_avatar_inittab.datas[info[2]]["moveSpeed"]
  46. avatar.accountEntity = self
  47. self.activeAvatar = avatar
  48. self.giveClientTo(avatar)

这里需要注意的是,baseRef.id指的是实体在内存中的id,base.dbid指的是数据库的id。

至此,其他方法都比较简单,就暂时不一一讲解。

思考两个问题:

1.怎么创建角色的时候进行重名判断

2.在控制台中详细调试了解对应的流程

我是青岛远硕信息科技发展有限公司的Peter,如果转载的话,请保留这段文字。

KBEngine warring项目源码阅读(三) 实体文件与Account处理的更多相关文章

  1. KBEngine warring项目源码阅读(一) 项目简介和注册

    首先介绍下warring项目,是kbe自带的一个演示示例,大部分人了解kbe引擎也是从warring项目开始的. 项目地址:https://github.com/kbengine/kbengine_u ...

  2. KBEngine warring项目源码阅读(二) 登录和baseapp的负载均衡

    原本不打算把登录拿出来写的,但是阅读登录部分的代码的时候发现登录和注册还不太一回事,因为登录涉及到分配baseapp的ip,负载均衡的实现,所以水一下. 流程图: 和上次一样,先是找unity控件 找 ...

  3. 25 BasicUsageEnvironment0基本使用环境基类——Live555源码阅读(三)UsageEnvironment

    25 BasicUsageEnvironment0基本使用环境基类——Live555源码阅读(三)UsageEnvironment 25 BasicUsageEnvironment0基本使用环境基类— ...

  4. 26 BasicUsageEnvironment基本使用环境——Live555源码阅读(三)UsageEnvironment

    26 BasicUsageEnvironment基本使用环境--Live555源码阅读(三)UsageEnvironment 26 BasicUsageEnvironment基本使用环境--Live5 ...

  5. 24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment

    24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment 24 UsageEnvironment使用环境抽象基类——Live555源码阅读 ...

  6. fw: 专访许鹏:谈C程序员修养及大型项目源码阅读与学习

      C家最近也有一篇关于如何阅读大型c项目源代码的文章,学习..融合.. -------------------- ref:http://www.csdn.net/article/2014-06-05 ...

  7. SparkSQL(源码阅读三)

    额,没忍住,想完全了解sparksql,毕竟一直在用嘛,想一次性搞清楚它,所以今天再多看点好了~ 曾几何时,有一个叫做shark的东西,它改了hive的源码...突然有一天,spark Sql突然出现 ...

  8. SpringMVC源码阅读(三)

    先理一下Bean的初始化路线 org.springframework.beans.factory.support.AbstractBeanDefinitionReader public int loa ...

  9. JDK源码阅读(三) Collection<T>接口,Iterable<T>接口

    package java.util; public interface Collection<E> extends Iterable<E> { //返回该集合中元素的数量 in ...

随机推荐

  1. linux日常管理-防火墙netfilter工具-iptables-3

    可以指定chain链的总开关 把链的关掉,针对端口开放,更加安全,但是不建议这么做 实例:filter表INPUT链.INPUT策略改成DROP. 把192.168.0.0/24网段开通22端口.对所 ...

  2. C# 处理Json

    下面是JSON对象转换为字符串 public static string ToJson(object obj) { try { JavaScriptSerializer serializer = ne ...

  3. IIS7.0(虚拟机)发布MVC5程序出现Http403错误的解决方法.

    近来,用MVC5开发自己的一个小网站.网上租用了一个小空间(虚拟主机),可选.net版本为2.0 3.0 3.5 4.0 ,上传网站 后发现是403错误.不能访问. 经与技术人员联系,把虚拟机更换到高 ...

  4. groupadd添加新组

    一.groupadd命令用于将新组加入系统. 格式groupadd [-g gid] [-o]] [-r] [-f] groupname 主要参数 -g gid:指定组ID号. -o:允许组ID号,不 ...

  5. 第八篇 elasticsearch链接mysql自动更新数据库

    增量更新 input { jdbc { jdbc_driver_library => "D:\tools\mysql\mysql-connector-java-5.1.45/mysql ...

  6. Hive 进阶

    两种情况下不走map-reduce: 1. where ds >' ' //ds 是partition 2. select * from table //后面没有查询条件,什么都没有 1.建表 ...

  7. C#对Execl操作类

    1.NuGet下安装 NPOI 2.实例代码:(可以根据具体情况注释和添加代码逻辑) public class ExeclHelper { /// <summary> /// 将excel ...

  8. 使用配置类而不使用XML文件(代替bean.xml)对spring进行配置

    以下类是一个配置类,它的作用和bean.xml是一样的注解: @Configuration 作用: 用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解. 获取容器时需要使用Anno ...

  9. Unity 着色器训练营(2) - MVP转换和法线贴图

    https://mp.weixin.qq.com/s/Qf4qT15s9bWjbVGh7H32lw 我们刚刚公布了Unity 2018.1中,Unity将会内置可视化编程工具Shader Graph, ...

  10. 我的省选 Day -13

    Day -13 10:18:46 早上360浏览器的网站一直显示 证书错误! 打开洛谷,一脸懵逼,网页根本不能正常显示.(一直到刚刚改了一下系统时间才恢复正常) 好在已经把昨天那道矩阵乘法的题目做完了 ...