目录

SDK自动生成

我们知道,通常的,客户端开发和服务端开发是完全分开的,中间相通或共用的部分会抽离出公共代码或使用工具进行一致性保证。这不但增加了额外的工作量,还有可能由于编码人员的失误而导致前后端无法匹配上,以至于各种各样的问题出现,甚至是很难察觉的问题。这对开发来说是非常痛心的!CBE引擎为了解决这一问题,专门制作了SDK自动生成器,使开发者面对不同的客户端引擎时都可以游刃有余。

该节内容概括:

引擎提供的SDK生成器会自动根据引擎开发过程中涉及的通讯协议、数据结构(包括自定义的数据结构)、Entity实体定义等方面与客户端SDK进行一一对应,保证高度一致性。

特点:

1.通讯协议一致性

通讯协议是引擎开发与客户端通讯保证一致性的基础。CBE引擎通过SDK生成器已经帮开发者自动处理了一致性问题。

1:使用我们提供的SDK生成器进行SDK的生成,引擎内置的所有基础通讯协议(如登录、握手等)就会一致,这个是自动的,无需开发者去关心。可以参见《自定义网络协议ID》一文。这里举个向Loginapp进行login请求的协议:

<Loginapp::login>
    <id>3</id>
    <descr>客户端请求登录到服务器的loginapp进程, 此进程收到请求验证合法后会返回一个网关地址。</descr>
    <arg>STRING</arg> <!-- 前端类别 0:调试前端, 1:手机前端, n.. -->
    <arg>UINT8_ARRAY</arg> <!-- 具体由开发者来解析 -->
    <arg>STRING</arg> <!-- 账号名 -->
    <arg>STRING</arg> <!-- 密码 -->
</Loginapp::login>

2:Entity实体的def配置中的Utype值会和自动客户端SDK中的一致。
我们知道Utype值是用来设置协议ID的(详情见《Entity包含哪些东西之Properties属性定义块》一文),同时必须保证全局唯一。引擎的SDK生成器会自动进行处理,保证客户端上所有的协议ID和服务器端的一致。

举个例子,一个name属性,Utype=41003,我们来看下客户端sdk上的值。

<root>
	<Properties>
        <!--名字-->
		<name>
			<Type>			UNICODE			</Type>
			<Utype> 		41003			</Utype>
			<Flags>			ALL_CLIENTS		</Flags>
			<Persistent>	true			</Persistent>
			<DetailLevel>	MEDIUM			</DetailLevel>
		</name>
    </Properties>
</root>

在AvatarBase.cs(该文件是根据服务器端的配置,被sdk生成器自动生成的Avatar实体的基类)中,我们看到:

public override void onUpdatePropertys(MemoryStream stream)
{
    ...
    switch(prop.properUtype)
	{
        ...
        case 41003:
            string oldval_name = name;
            name = stream.readUnicode();

            if(prop.isBase())
            {
                if(inited)
                    onNameChanged(oldval_name);
            }
            else
            {
                if(inWorld)
                    onNameChanged(oldval_name);
            }

            break;
        ...
    }
    ...
}

和服务端的配置一致,也是41003!


2.数据结构一致性

不单单通讯协议高度保持一致,在服务器端上所有的数据结构也被一致化了!我们拿一个自定义的数据结构AVATAR_INFO为例:

在服务端的types.xml中
<!-- AVATAR信息 -->
<AVATAR_INFO>	FIXED_DICT
    <implementedBy>AVATAR_INFO.avatar_info_inst</implementedBy>
    <Properties>
        <dbid>
            <Type>	DBID	</Type>
        </dbid>
        <name>
            <Type>	UNICODE	</Type>
            <DatabaseLength> 256 </DatabaseLength>
        </name>
        <roleID>
            <Type>	UINT8	</Type>
        </roleID>
        <level>
            <Type>	UINT16	</Type>
        </level>
    </Properties>
</AVATAR_INFO>

可以看到有UINT64的dbid、string型的name、UINT8型的roleID、UINT16型的level属性。我们来看看客户端上的代码:

在客户端的KBETypes.cs中
public class AVATAR_INFO
{
    public UInt64 dbid = 0;
    public string name = "";
    public Byte roleID = 0;
    public UInt16 level = 0;

}

看到了这些,是不是和服务器端一模一样?SDK生成器就是在背后所有的这些事情,让开发者更加放心更加省心!


3.实体定义一致性

所有和客户端相关的方法、属性(暴露给客户端的方法、客户端暴露给服务端的方法、对客户端可见的属性)都一一对应了起来!

还是拿在上一节《客户端编程概述》中举的例子:

...
<Properties>
    <!--角色的字典-->
    <characters>
        <Type>			AVATAR_INFO_LIST	</Type>
        <Flags>			BASE				</Flags>
        <Default>							</Default>
        <Persistent>	true				</Persistent>
    </characters>
</Properties>

<BaseMethods>
    <!-- 暴露给客户端的请求角色列表接口 -->
    <reqAvatarList>
        <Exposed/>
    </reqAvatarList>
    <!-- 暴露给客户端的请求创建角色接口 -->
    <reqCreateAvatar>
        <Exposed/>
        <!-- roleType -->
        <Arg>		    UINT8	        </Arg>
        <!-- name -->
        <Arg>		    UNICODE	        </Arg>
    </reqCreateAvatar>
    <!-- 测试-不给client暴露的方法 -->
    <canNotCalledByClient>
    </canNotCalledByClient>
</BaseMethods>
<!-- 客户端远程方法 -->
<ClientMethods>
    <!-- 请求avatar列表后,返回给客户端 -->
    <onReqAvatarList>
        <!--返回的角色列表-->
        <Arg>	        AVATAR_INFO_LIST</Arg>
    </onReqAvatarList>
    <!-- 请求创建avatar后,成功的回调 -->
    <onCreateAvatarSuccess>
        <!-- 创建出的角色信息 -->
        <Arg>	        AVATAR_INFO     </Arg>
    </onCreateAvatarSuccess>
    <!-- 请求创建avatar后,失败的结果 -->
    <onCreateAvatarFailed>
        <!--错误代码-->
        <Arg>	        INT8	        </Arg>
    </onCreateAvatarFailed>
</ClientMethods>

这样的配置下,我们看看客户端生成了什么?是否可以匹配起来?我们来看看AccountBase.cs文件(该文件就是对应服务端Account实体的基类):

public abstract class AccountBase : Entity
{
    public EntityBaseEntityCall_AccountBase baseEntityCall = null;
    public EntityCellEntityCall_AccountBase cellEntityCall = null;

    public abstract void onCreateAvatarFailed(SByte arg1); 
    public abstract void onCreateAvatarSuccess(AVATAR_INFO arg1); 
    ...
    public abstract void onReqAvatarList(AVATAR_INFO_LIST arg1); 
}

发现好像没有属性characters?这是因为该属性的FLAGS设置的是BASE,相当于客户端不可见,所以SDK没有生成该属性(请参见《Entity包含哪些东西之Properties属性定义块》一文)。而ClientMethods中的onReqAvatarListonCreateAvatarFailedonCreateAvatarSuccess都已出现。

BaseMethods中的reqAvatarListreqCreateAvatarcanNotCalledByClient在哪里呢?我们继续看代码,进入EntityBaseEntityCall_AccountBase类里:

public class EntityBaseEntityCall_AccountBase : EntityCall
{
    ...
    public void reqAvatarList()
    {
        Bundle pBundle = newCall("reqAvatarList", 0);
        if(pBundle == null)
            return;

        sendCall(null);
    }
    public void reqCreateAvatar(Byte arg1, string arg2)
    {
        Bundle pBundle = newCall("reqCreateAvatar", 0);
        if(pBundle == null)
            return;

        bundle.writeUint8(arg1);
        bundle.writeUnicode(arg2);
        sendCall(null);
    }
}

可以看到,reqAvatarListreqCreateAvatar都已经被定义,那canNotCalledByClient怎么还是没有呢?别急,因为该方法没有设置Exposed标签,所以是不暴露给客户端的,因此不会在被SDK生成器生成。这样大家都好理解了吧。

同样的,如果CellMethods中声明了可以暴露给客户端的方法时,会在EntityCellEntityCall_AccountBase中进行定义。

接下来我们看看如何进行SDK的生成。


生成SDK–gensdk.bat脚本:

很简单,执行{项目资产库}/gensdk.bat的批处理脚本即可!启动后会经过一系列操作,并提示完成即可。

我们来看下该脚本:

@echo off
set curpath=%~dp0

cd ..
set KBE_ROOT=%cd%
set KBE_RES_PATH=%KBE_ROOT%/kbe/res/;%curpath%/;%curpath%/scripts/;%curpath%/res/
set KBE_BIN_PATH=%KBE_ROOT%/kbe/bin/server/

if defined uid (echo UID = %uid%) else set uid=%random%%%32760+1

echo KBE_ROOT = %KBE_ROOT%
echo KBE_RES_PATH = %KBE_RES_PATH%
echo KBE_BIN_PATH = %KBE_BIN_PATH%

cd %curpath%
start %KBE_BIN_PATH%/kbcmd.exe --clientsdk=unity --outpath=D:\kbe\kbengine_unity3d_demo\Assets\Plugins\kbengine\kbengine_unity3d_plugins

之前几行都是定义一些路径,我们看最后一行,执行kbcmd.exe,并指定clientsdk为unity,意思是使用UNITY为输出目标,生成器会自动选择合适的生成模板和逻辑进行生成。再指定outpath为输出的路径。

修改目标客户端类型

只要修改clientsdk后面的值即可。

目前支持Unity、UnrealEngine4,而其他引擎会在后续版本中添加。

修改输出路径

因为客户端项目的路径是不确定的,一般需要开发者自行进行设置,只需要修改刚才的outpath的值即可。


接下来我们来看看SDK生成出来的文件夹是什么样的结构。


SDK文件夹结构介绍:

我们可以先看一下在VisualStudio中的“资源管理器”列表:

1

如图所示,默认的gensdk.bat会生成到Assets/Plugins/kbengine/kbengine_unity3d_plugins下。

下面有几个生成时的规则可循:

1.实体的对应基类是以”{实体名}Base”来命名的

如刚才的图中,我们看到AccountBase.cs,这个是默认最小资产库中Account实体对应的客户端实现,并且是一个基类,

2.实体的对应基类的实现类必须为”{实体名}”来命名

实体基类,如AccountBase.cs必须在客户端编码时进行继承,并命名为Account

注意:命名为Account类,这是一个内部机制,在客户端sdk初始化时会查找对应实体的实现类,而查找的方式是通过与服务端实体名(如Account实体)对应的类名。

3.与实体类似,实体的EntityCall也是如此来命名的

如刚才提到的Account实体,它对应的EntityCall都会写在名为EntityCallAccountBase中,包括base和cell的。

4.组件的命名规则和实体类似

这里不再赘述。

文件夹结构介绍:

除去和业务相关的实体以及对应EntityCallxxx,该文件夹下有:
AccountBase.cs:默认的最小资产库是有Account实体的,所以会生成该文件,用于和服务端实体对应。
Bundle.cs:这个模块将多个数据包打捆在一起。
CustomDataTypes.cs:所有自定义数据类型的流转换类,都在该文件下。即从通讯时的数据流中创建出某种类型以及把某类型写入到流中。
DataTypes.cs:引擎支持的基本数据类型,都在该文件下声明。
Dbg.cs:debug模块。
Entity.cs:KBEngine逻辑层的实体基础类,所有扩展出的游戏实体都应该继承于该模块。
EntityCall.cs:实体的EntityCall基类。
EntityComponent.cs:KBEngine逻辑层的实体组件的基础类,所有扩展出的组件对象都应该继承于该模块。
EntityDef.cs:与引擎端的EntityDef对应,描述所有的def配置。
Event.cs:事件系统。为了剥离前后端业务,两者解耦,通过该系统进行通讯。
KBEMain.cs:插件的入口模块,通过继承它可以直接初始化sdk配置。
KBEngine.cs:KBEngine插件的核心模块,包括网络创建、持久化协议、entities的管理、以及引起对外可调用接口。
KBEngineArgs.cs:初始化KBEngine的参数类。
KBETypes.cs:SDK中所有数据类型,和服务端kbe/scripts/entity_defs/types.xml对应。
Math.cs:KBEngine的数学相关模块。
MemoryStream.cs:二进制数据流模块。能够将一些基本类型序列化(writeXXX)成二进制流同时也提供了反序列化(readXXX)等操作。
MessageReader.cs:消息阅读模块,从数据包流中分析出所有的消息包并将其交给对应的消息处理函数。
Messages.cs:所有通讯消息,都在此文件下定义。
Method.cs:实体定义的方法的基类。
NetworkInterface.cs:网络模块,处理连接、收发数据。
ObjectPool.cs:简单的对象池实现。
PacketReceiver.cs:包接收模块(与服务端网络部分的名称对应),处理网络数据的接收。
PacketSender.cs:包发送模块(与服务端网络部分的名称对应),处理网络数据的发送。
Profile.cs:分析模块,有简单的start和end方法。
Property.cs:抽象出一个entitydef中定义的属性基类,该模块描述了属性的id以及数据类型等信息。
ScriptModule.cs:一个entitydef中定义的脚本模块的基类,包含了某个entity定义的属性与方法以及该entity脚本模块的名称与模块ID。
ServerErrorDescrs.cs:服务器错误描述类,与{项目资产库}/res/server/server_errors.xml对应。


Copyright © 2018 Yolo Technologies. Publication: 2.0-025. Built: 2018-12-07.