Skip to content

Cocos Creator的实现

本文将会使用Cocos Creator来完成上一节中的客户端实现。

Cocos Creator版本

本教程中使用的版本为Cocos Creator 3.8.6,TypeScript语言。

先附上客户端Cocos Creator的源代码:点我下载(里面包含了对应的服务端源代码)。

Cocos Creator是一款轻量级、高效、免费开源的跨平台游戏开发引擎,支持2D和3D游戏开发,使用TypeScript作为脚本语言。

第一步:生成客户端SDK

KBEngine提供了专属的SDK生成器,它是为了方便开发者而专门制作的工具,使开发者面对不同的客户端引擎时都可以游刃有余。

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

介绍完SDK生成器,我们来看看如何修改配置,使其对应Cocos Creator引擎。

1. 修改SDK生成路径:

在本项目的资产库"getstarted_assets"下,找到gensdk.bat,使用编辑工具或记事本打开,找到最后一行:

bat
start %KBE_BIN_PATH%/kbcmd.exe --clientsdk=typescript --outpath=%curpath%/kbengine_typescript_plugins

其中: clientsdk:指定输出SDK的客户端类型,这里填写typescript。Cocos Creator使用TypeScript作为脚本语言,生成器会自动选择合适的生成模板和逻辑进行生成。

outpath:指定SDK的输出路径。请确保路径是在你Cocos Creator工程的对应目录下。

TIP

如果引擎脚本开发工程师和客户端工程师是同一人,或者为了立刻验证SDK的结果是否如预期,我们建议outpath设置成Cocos Creator工程下的对应目录,如:your_project/assets/scripts/kbe_typescript_plugins。每次生成SDK后可直接切回客户端进行测试,减少了重复粘贴复制的操作。

2. 执行工具,生成SDK代码:

编辑完成后保存退出,双击执行gensdk.bat。

等待生成完毕,进入客户端对应的文件夹查看。

3. 文件夹结构介绍:

生成完成后,kbe_typescript_plugins文件夹下包含以下核心文件:

文件/模块说明
KBEngine.ts核心引擎入口,KBEngineApp单例,管理连接、实体生命周期、消息分发
NetworkInterface.tsWebSocket网络层,负责与服务器通讯
EntityDef.ts所有数据类型、实体方法、属性的自动生成定义
Messages.ts协议消息ID和处理器映射
Event.ts事件系统,支持 注册/注销/暂停/恢复
FirstEntityBase.tsFirstEntity实体的抽象基类(自动生成)
EntityCallFirstEntityBase.tsFirstEntity的远程调用代理(自动生成)
ExportEntity.ts实体注册表,RegisterScript / GetEntityScript

生成规则:

1、服务端定义了有客户端部分的实体(声明了hasClient="true"的),则会生成类似实体名+Base.ts的文件,它是一个抽象类,我们只需继承它、实现它,并使用实体名为类名即可,如这里的FirstEntityBase,该类中会包含def中声明的客户端方法,如本教程中的onEnteronSay

2、被生成的实体,会对应包含一个类似EntityCall+实体名+Base.ts的文件,该文件是对应实体的EntityCall的实现。该类中会包含该实体的被暴露给客户端的通讯方法(被设置了Exposed标签的),如FirstEntity在def中声明的say方法。

第二步:实现Client部分

1. 客户端设计概述

服务器回顾:

先来回顾下本教程的服务器设计,我们把FirstEntity与账户入口关联,使得客户端一旦连接服务器并通过登录认证后就会创建出FirstEntity实体,此时该实体的客户端部分也会被创建。一旦创建完毕后会被立即传送到FirstSpace所在空间中去,完成后会通过onEnter的远程方法通知客户端。接着,客户端向服务器发出say请求后,服务器会进行广播,并通过客户端的onSay方法告知所有在同一空间的客户端。

客户端设计:

我们分为两个场景,一个叫做scene/login登录场景,默认打开,负责与服务器连接、登录认证。一旦成功登录并进入空间后,服务器会调用FirstEntity的客户端远程方法onEnter,从该方法的实现中让客户端进入另一个场景scene/world,其负责say的发送以及处理onSay的远程调用。

好了,让我们开始动手吧!

2. 实现登录场景

2.1 场景制作

scene/login场景需要两个关键节点:

  • ServerNode:挂载ServerNode脚本的节点,是引擎SDK的入口,负责初始化KBEngineApp并保持持久化。需要将其设为常驻节点(Persist Root Node),以便在场景切换时不被销毁。

  • LoginLayer:登录界面的Canvas节点,包含用户名/密码输入框(EditBox组件)和登录按钮(Button组件),挂载LoginLayer脚本。

UI效果图如下(参考Unity章节的登录界面设计即可,使用Cocos Creator的UI系统实现)。

2.2 实现ServerNode(SDK入口)

ServerNode是客户端连接服务器的核心入口,在onLoad中初始化KBEngine SDK并注册事件:

typescript
import { KBEngineApp, KBEngineArgs } from "./kbe_typescript_plugins/KBEngine";
import KBEEvent, { EventOutTypes } from "./kbe_typescript_plugins/Event";
import { _decorator, Component, director } from 'cc';

const { ccclass } = _decorator;

@ccclass
export class ServerNode extends Component {
    ip = "127.0.0.1";

    onLoad() {
        this.installEvents();
        this.initServerApp();
        // 设为常驻节点,场景切换不销毁
        director.addPersistRootNode(this.node);
    }

    installEvents() {
        KBEEvent.Register("onDisconnected", this, this.onDisconnected.bind(this));
        KBEEvent.Register("onKicked", this, this.onKicked.bind(this));
        KBEEvent.Register(EventOutTypes.addSpaceGeometryMapping,
            this, this.addSpaceGeometryMapping.bind(this));
    }

    initServerApp(): void {
        let args = new KBEngineArgs();
        args.address = this.ip;
        args.port = 20013;
        KBEngineApp.Destroy();
        KBEngineApp.Create(args);
    }

    addSpaceGeometryMapping(path: string) {
        // 暂停事件派发,加载完成后再启用
        KBEEvent.Pause();
        director.loadScene("scene/world", () => {
            KBEEvent.Resume();
        });
    }

    onDisconnected() {
        KBEngineApp.Destroy();
        director.loadScene("scene/login");
    }

    onKicked(failedcode: number) {
        KBEngineApp.Destroy();
        director.loadScene("scene/login");
        this.initServerApp();
    }
}

要点说明:

  • initServerApp()中通过KBEngineArgs设置服务器IP和端口,然后调用KBEngineApp.Create(args)创建SDK实例。
  • addSpaceGeometryMapping是进入空间后服务器触发的回调,我们在此切换到scene/world场景。注意在场景切换期间需要暂停事件系统KBEEvent.Pause()),加载完成后再恢复,避免实体数据在场景未就绪时到达。
  • 将ServerNode节点设为addPersistRootNode常驻节点,确保跨场景切换时SDK不会被销毁。

2.3 实现LoginLayer(登录界面)

登录界面的脚本负责处理登录按钮的点击:

typescript
import { _decorator, Component, EditBox } from 'cc';
import { KBEngineApp } from "../../kbe_typescript_plugins/KBEngine";

const { ccclass, property } = _decorator;

@ccclass('LoginLayer')
export class LoginLayer extends Component {
    @property(EditBox)
    loginNameBox: EditBox = null;

    @property(EditBox)
    loginPassBox: EditBox = null;

    // 登录按钮点击
    onButtonLogin() {
        KBEngineApp.app.Login(
            this.loginNameBox.string,
            this.loginPassBox.string,
            "kbengine_unity3d_demo"
        );
    }
}

这里主要调用了API中的Login方法进行了登录请求,该API会先进行服务器连接,成功后调用登录方法。第三个参数是客户端的额外数据。

3. 实现FirstEntity

按照刚才的设计,登录成功后,FirstEntity的客户端部分会被创建,也就是说客户端上FirstEntity对象会被创建,接着进入空间后,会通过FirstEntity.onEnter通知客户端,所以我们来实现一下FirstEntity的客户端部分。

FirstEntityBase是对应服务端的FirstEntity实体的,我们实现它即可完成其客户端部分。

typescript
import { FirstEntityBase } from "../kbe_typescript_plugins/FirstEntityBase";
import { RegisterScript } from "../kbe_typescript_plugins/ExportEntity";
import { director, Label, find } from 'cc';

export class FirstEntity extends FirstEntityBase {

    __init__() {
        super.__init__();
    }

    onEnter(): void {
        // 当进入空间后,加载世界场景
        director.loadScene("scene/world");
    }

    onSay(content: string): void {
        // 找到UI Label节点并显示内容
        const label = find("Canvas/Label")?.getComponent(Label);
        if (label) {
            label.string = label.string + "\n" + content;
        }
    }
}

// 注册实体类,使SDK能够通过类名创建实例
RegisterScript("FirstEntity", FirstEntity);

onEnter:我们注意到,该函数名和服务端FirstEntity实体的DEF配置文件中的client部分定义的一模一样!对,这就是SDK生成器帮你做的事情。在生成的FirstEntityBase类中使用抽象方法定义了该方法,并由SDK内部进行了通讯上的对应和处理,我们只需要在继承类FirstEntity中实现即可。

onSay:该方法也是和DEF配置文件中的client部分一样,并且连方法签名也是一致的!

注意

每个被指定有Client部分的实体,在客户端上必须要有实现类且类名和实体名字一致。比如这里的FirstEntity : FirstEntityBase,不能把类名修改成其他,如FirstEntity2就会在客户端SDK启动时报错提示。

4. 实现世界场景

4.1 场景制作

scene/world场景中需要:

  • HelloWorld节点:包含一个Button按钮和一个Label文本显示区域,挂载HelloWorld脚本。

4.2 实现HelloWorld

typescript
import { _decorator, Component } from 'cc';
import { KBEngineApp } from "../kbe_typescript_plugins/KBEngine";
import { FirstEntity } from "../entities/FirstEntity";

const { ccclass } = _decorator;

@ccclass('HelloWorld')
export class HelloWorld extends Component {

    // 按钮点击事件
    onButtonClick() {
        // 通过API:player()获得账户自己的实体
        const entity = KBEngineApp.app.player() as FirstEntity;
        // 由于say方法是在cell上的远程方法,这边使用cellEntityCall属性来调用
        entity.cellEntityCall.say("hello world");
    }
}

这里主要调用了API中的player()方法获取到客户端自身的账户实体,并转成了FirstEntity类型。同时调用了该实体上的cellEntityCall.say方法来调用服务器端的对应远程方法,完成向服务器的请求。

对!刚才的onEnteronSay,包括这里的cellEntityCall.say,与服务端一一对应的这一切事情,都是由SDK生成器帮你完成的!

接下来,让我们迎来激动人心的时刻!服务器和客户端的联通验证!

第三步:验证

1. 启动引擎

在本项目的资产库"getstarted_assets"下,找到start_server.bat,并双击运行。

等待所有服务器组件的窗口都出现"Found all the components!"字样,就说明成功启动了。

2. 运行客户端

2.1 打开Cocos Creator编辑器,打开工程。

2.2 在编辑器中打开scene/login场景。

2.3 点击编辑器顶部的播放按钮(或按Ctrl+P),启动预览。

3. Hello world

1、启动后,出现登录窗口,随意输入账号和密码(长度都要大于4位),点击Login按钮,就会向服务器发出登录请求。

Tips

本教程中,服务端没有对账户验证做处理,所以任意的账号密码都可以登录成功。 整个过程细节,可以查看客户端中Console窗口的日志。

2、一旦登录成功,会跳转至scene/world场景,里面只有一个UI——HelloWorld按钮。

3、点击hello world按钮,会向服务端发起say的远程调用。

4、收到onSay的通知后,会在UI显示出文字。

TIP

显示内容格式为"Entity: " + self.id + content,这是服务端FirstEntity中定义好的格式。

恭喜你,Cocos Creator的客户端实现已完成!

通过Cocos Creator客户端的实现,我们利用FirstEntity实体的Client部分与服务器建立了连接,并立即进入了FirstSpace所在的空间中,接着,在空间内我们向第一个实体FirstEntity进行了say的操作,并收到了onSay的广播。

这是GetStarted章节中客户端Cocos Creator的源代码:点我下载(里面包含了对应的服务端源代码)

开发者肯定对整个的通讯过程、业务流程还存在一些疑问,让我们进入下一节《GetStarted总结》中进行回顾、梳理和总结吧。

点我进入《GetStarted总结》