cocos creator 使用websocket实现简单的在线聊天功能

效果

1、打开两个窗口,输入名称进行登陆

2、开始聊天 

思路

做好三件事:

一、写好cocos的界面

二、搭建本地服务器

三、写好cocos的脚本

步骤

一、写好cocos的界面

主要有两个,一个是登陆面板(login节点),一个是聊天面板(chat节点)。

下图是登陆面板。

下图是聊天面板

需要注意一个细节,滑动视图的content要添加垂直布局,不然聊天记录会重叠在一起。

同时还需要将chat--New ScrollView--view--content--item制作成预制。因为后面要不断地产生聊天记录,就是用item来生成的。做成预制之后就可以把item节点给删掉。

 

这样界面就好了,接下来搭建本地服务器。

 

二、搭建本地服务器

当你在玩单机游戏的时候,你的电脑会响应你的一切操作,但是如果要玩多人游戏,则需要远程的服务器来同步你和别的玩家的状态。远程服务器是要花钱的,所以我们可以搭建一个简单的本地服务器。这里要用到Node.js,它是一个平台,具体干嘛的我也说不清。先去node官网下载它吧,然后终端检测一下是否安装成功。

node -v
// v12.14.0

接着就开始搭建服务器啦。找一个你喜欢的位置新建文件夹ChatServer,再在这个文件夹下新建文件夹Server,在此目录下运行如下命令

npm install nodejs-websocket

继续在Server目录下新建server.js,写一个每当有客户端连接时就会打印的功能。

// server.js
// 导入模块,使用ws变量接收
var ws = require("nodejs-websocket");
// createServer方法会在每个连接到来时执行一次
// 其中conn参数为到来的连接
var server = ws.createServer(function (conn) {
    console.log("一个新的来自客户端的连接!");
}).listen(3000);

我们让这个服务器运行起来,在Server目录下执行命令:

node server

 

执行了不会有什么反应,但是服务器已经运行起来了。接下来写cocos客户端的脚本,我们会在下面的内容检验客户端能否连上这个正在运行的服务器。

 

三、写好cocos的脚本

1、连接服务器

在cocos中我们只需要写一个JS脚本,我把它命名为Main,放在scripts目录下,挂到Canvas节点上。在下文中我可能直接把这个脚本说成客户端代码。

学过网络编程的同学都知道,客户端开始运行的时候,要和指定的服务器进行连接,因此start函数写为如下,一旦客户端成功连上服务器端就打印“连接成功”。

// Main.js
cc.Class({
    extends: cc.Component,

    properties: {

    },

    start () {
        let self = this;
        // 进行本地连接,3000 端口
        self.ws = new WebSocket("ws://localhost:3000");
        self.ws.onopen = function (event) {
            console.log("连接成功!");
        };
    },

});

cocos运行一下,注意这里要确保第二步的服务器正在运行。

可以看到客户端能连上服务器,打印了连接成功。同时服务器也进行了打印。

这样,我们的网络模块就搞通了,接下来要继续完善客户端代码。

2、动态生成item预制体

第一步说了,聊天时需要不断地产生item预制体,这里引用一下官方文档的说法:

在运行时进行节点的创建(cc.instantiate)和销毁(node.destroy)操作是非常耗费性能的,因此我们在比较复杂的场景中,通常只有在场景初始化逻辑(onLoad)中才会进行节点的创建,在切换场景时才会进行节点的销毁。如果制作有大量敌人或子弹需要反复生成和被消灭的动作类游戏,我们要如何在游戏进行过程中随时创建和销毁节点呢?这里就需要对象池的帮助了。 

因此我们的代码onLoad里面需要用到对象池,以降低性能的耗费。所以onLoad函数这么写:

// Main.js
cc.Class({
    extends: cc.Component,

    properties: {
        // item预制体
        item: cc.Prefab,
    },

    onLoad () {
        // 初始化对象池,使用 this.pool 变量接收
        this.pool = new cc.NodePool();
        for (let i = 0; i < 200; i++) {
            // 克隆预制体,用 item_prefab 变量接收
            let item_prefab = cc.instantiate(this.item);
            // 放入对象池中,在需要时使用 get() 方法即可
            this.pool.put(item_prefab);
        }
    },

    start () {
        let self = this;
        // 进行本地连接,3000 端口
        self.ws = new WebSocket("ws://localhost:3000");

        self.ws.onopen = function (event) {
            console.log("连接成功!");
        };
    },

});

为了测试一下我们能不能动态生成item,在start里面调用新写的createMessage方法。

// Main.js
cc.Class({
    extends: cc.Component,

    properties: {
        // item预制体
        item: cc.Prefab,
        // 聊天记录的容器
        content: cc.Node,
    },

    onLoad () {
        ...
    },

    start () {
        // 测试能否动态生成item
        this.createMessage('你好');
        this.createMessage('我很好');

        let self = this;
        // 进行本地连接,3000 端口
        self.ws = new WebSocket("ws://localhost:3000");

        self.ws.onopen = function (event) {
            console.log("连接成功!");
        };
    },

    // 创建一个聊天记录,传入参数为字符串
    createMessage (str) {
        let item_prefab = null;
        // 先判断对象池中取没取空
        if (this.pool.size() > 0) {
            item_prefab = this.pool.get();
        } else {
            item_prefab = cc.instantiate(this.item);
        }
        // 为 item_prefab 指定父节点,更改其显示的字符串
        item_prefab.parent = this.content;
        item_prefab.getComponent(cc.Label).string = str;
    },

});

注意把item预制体和content节点拖动关联。

然后运行,就是预期的效果。

3、登陆与聊天的实现

首先是登陆,登陆时用户需要输入名称,这个名称给服务器存着。因此需要给输入框和按钮加上响应事件。输入框事件将name保存起来,按钮事件将name发送给服务器。

// Main.js
cc.Class({
    
    properties: {
        ...
        // 登陆页面
        login: cc.Node,
    },
    ...
    
    start () {

        ...

        // 监听服务端发来消息
        self.ws.onmessage = function (event) {
            console.log(event.data);
            // 创建消息
            self.createOneLab(event.data);
        };
    },

    // 用户起名框输入完成后
    name_onEditDidEnded (editBox) {
        // this.name 用于接收你输入的昵称
        this.name = editBox.string;
    },

    // 登入按钮点击
    loginClick () {
        // 如果为空或者未定义或者为空字符串,返回
        if (!this.name || this.name == '') {
            return;
        }
        // 为了与后面的聊天信息区分,我们约定 0为昵称,1为聊天信息
        this.ws.send("0:" + this.name);
        // 然后隐藏登入页
        this.login.active = false;
    },

});

把事件挂上去,这里指展示了输入框的,按钮的一样的,省略了。

接下来写服务端代码,服务器收到昵称,发送广播至所有已连接的客户端,告诉他们该用户加入了房间。

// server.js
// 导入模块,使用ws变量接收
var ws = require("nodejs-websocket");
// createServer方法会在每个连接到来时执行一次
// 其中conn参数为到来的连接,listen是监听的端口
var server = ws.createServer(function (conn) {
    console.log("一个新的来自客户端的连接!");

    // 监听客户端发来的字符串数据
    conn.on("text", function (str) {
        // 首数字为0时
        if (str[0] == '0') {
            // 以:分割
            var s = str.split(":");
            // 打印昵称
            console.log("客户端发来昵称:" + s[1]);
            // 存储昵称至conn.name
            conn.name = s[1];
            // 服务器广播消息至所有客户端
            broadcast(server,conn.name + "加入房间!");
        }
    });
}).listen(3000);

// 向目前连接的所有客户端发送消息
function broadcast(server, msg) {
    server.connections.forEach(function (conn) {
        conn.sendText(msg);
    })
}

为了防止服务器报错而停止运行,正在conn.on方法下面再补充两个出现错误和连接断开的方法。

// server.js
...
var server = ws.createServer(function (conn) {
    ...

    // 监听错误出现与连接断开
    conn.on("error", function () {
        console.log("出现错误!");
    });
    conn.on("close", function () {
        console.log("连接断开!");
    });
    
}).listen(3000);

...

运行服务器,运行客户端,在客户端输入“花花”并发送,

服务器收到“花花”并打印,广播给每个已连接的客户端,

 客户端收到服务器的广播,打印“花花加入房间!”。

接下来就是聊天了,和登陆发送呢称是类似的。接下来放出服务器和客户端的完整代码供读者参考。

服务器

// 导入模块,使用ws变量接收
var ws = require("nodejs-websocket");
// 使用createServer方法创建一个连接   .listen(端口号)
// 该方法会在每个连接到来时执行一次
var server = ws.createServer(function (conn) {
    // 其中conn参数为到来的连接
    console.log("一个新的来自客户端的连接!");

    // 监听客户端发来的字符串数据
    conn.on("text", function (str) {
        // 首数字为0时
        if (str[0] == '0') {
            // 以:分割
            var s = str.split(":");
            // 我们打印出来
            console.log("客户端发来昵称:" + s[1]);
            // 存储昵称至conn.name
            conn.name = s[1];
            // 服务器广播消息至所有客户端
            broadcast(server,conn.name + "加入房间!");
        } else if (str[0] == '1') {
            var s = str.split(":");
            // 我们打印出来
            console.log("客户端发来消息:" + s[1]);
            // 服务器广播消息至所有客户端
            broadcast(server,conn.name + ":" + s[1]);
        }
    });

    // 监听错误出现与连接断开
    conn.on("error", function () {
        console.log("出现错误!");
    });
    conn.on("close", function () {
        console.log("连接断开!");
    });
}).listen(3000);

// 方法: 向目前连接的所有客户端发送消息
function broadcast(server, msg) {
    server.connections.forEach(function (conn) {
        conn.sendText(msg);
    })
}

// 打印,省的控制台空旷
console.log('本地服务端开始运行!');

客户端

// Main.js
cc.Class({
    extends: cc.Component,

    properties: {
        // item预制体
        item: cc.Prefab,
        // 聊天记录的容器
        content: cc.Node,
        // 登陆页面
        login: cc.Node,
        // 文本框
        input: cc.Node,
    },

    onLoad () {
        // 初始化对象池,使用 this.pool 变量接收
        this.pool = new cc.NodePool();
        for (let i = 0; i < 200; i++) {
            // 克隆预制体,用 item_prefab 变量接收
            let item_prefab = cc.instantiate(this.item);
            // 放入对象池中,在需要时使用 get() 方法即可
            this.pool.put(item_prefab);
        }
    },

    start () {

        let self = this;
        // 进行本地连接,3000 端口
        self.ws = new WebSocket("ws://localhost:3000");

        self.ws.onopen = function (event) {
            console.log("连接成功!");
        };

        // 监听服务端发来消息
        self.ws.onmessage = function (event) {
            console.log(event.data);
            // 创建消息
            self.createMessage(event.data);
        };
    },

    // 创建一个聊天记录,传入参数为字符串
    createMessage (str) {
        let item_prefab = null;
        // 先判断对象池中取没取空
        if (this.pool.size() > 0) {
            item_prefab = this.pool.get();
        } else {
            item_prefab = cc.instantiate(this.item);
        }
        // 为 item_prefab 指定父节点,更改其显示的字符串
        item_prefab.parent = this.content;
        item_prefab.getComponent(cc.Label).string = str;
    },

    // 用户起名框输入完成后
    name_onEditDidEnded (editBox) {
        // this.name 用于接收你输入的昵称
        this.name = editBox.string;
    },

    // 登录按钮点击
    loginClick () {
        // 如果为空或者未定义或者为空字符串,返回
        if (!this.name || this.name == '') {
            return;
        }
        // 我们约定 0: 类型为昵称,1: 类型为聊天信息
        this.ws.send("0:" + this.name);
        // 然后隐藏登入页
        this.login.active = false;
    },

    // 输入聊天信息后
    text_onEditDidEnded (editBox) {
        // this.text 用于接收你输入的聊天文本
        this.text = editBox.string;
    },

    // 发送按钮点击
    sendClick () {
        // 如果为空或者未定义或者为空字符串,返回
        if (!this.text || this.text == '') {
            return;
        }
        // 我们约定 0: 类型为昵称,1: 类型为聊天信息
        this.ws.send("1:" + this.text);
        // 然后重置text为空字符
        this.text = '';
        this.input.getComponent(cc.EditBox).string = '';
    },

});

效果就如开头所述。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值