zoukankan      html  css  js  c++  java
  • 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文档里是这么解释的

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

        <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 = 0,        // CELL_MAILBOX
                MAILBOX_TYPE_BASE = 1        // BASE_MAILBOX
            }
            
            public Int32 id = 0;
            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!
    ",                                            
                scriptName(), id()));
    
            s.done();
            return;                                                                                            
        }
    
        ENTITY_METHOD_UID utype = 0;
        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}.
    ", 
                utype, id_, this->scriptName()));
            
            s.done();
            return;
        }
    
        // 如果是外部通道调用则判断来源性
        if (pChannel->isExternal())
        {
            ENTITY_ID srcEntityID = pChannel->proxyID();
            if (srcEntityID <= 0 || srcEntityID != this->id())
            {
                WARNING_MSG(fmt::format("{2}::onRemoteMethodCall({3}): srcEntityID:{0} != thisEntityID:{1}.
    ",
                    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}.
    ",
                    pMethodDescription->getName(), srcEntityID, this->scriptName()));
    
                s.done();
                return;
            }
        }
    
        if(g_debugEntity)
        {
            DEBUG_MSG(fmt::format("{3}::onRemoteMethodCall: {0}, {3}::{1}(utype={2}).
    ", 
                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() == 0)
            {
                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[0]);
            }

    服务器

        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.
    " % (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.
    " % (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'1'})])
                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,如果转载的话,请保留这段文字。

  • 相关阅读:
    中文词频统计及词云制作
    字符串操作练习:星座、凯撒密码、99乘法表、词频统计预处理
    python第二节课
    Python第一节课
    了解大数据
    pcb结构定义
    一个完整的大作业
    中文词频统计及词云制作
    字符串操作练习:星座、凯撒密码、99乘法表、词频统计预处理
    Python输入输出练习
  • 原文地址:https://www.cnblogs.com/lsm19870508/p/6924396.html
Copyright © 2011-2022 走看看