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!\n",                                            
            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}.\n", 
            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}.\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() == 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.\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'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,如果轉載的話,請保留這段文字。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章