学Cocos Creator 3.8.0链接GoWorld自带示例ChatRoom

下载Cocos Dashboard

创建按一个3D项目

场景里添加2个UI对象,按钮,编辑框

左下添加一个TS脚本NewComponent添加到Canvas上作为按钮的组件

TS里写

   onBtnClick(event, customEventData){var editBox = this.node.getChildByName("EditBox").getComponent(EditBox);console.log('按钮按下'+editBox.textLabel.string);}

然后按钮事件挂自己的按钮节点,选择这个脚本的这个方法。

点上面三角形运行后会直接打开Edge浏览器,F12打开浏览器调试分割窗口,点击按钮看到右边出现“按钮按下”文字

场景名字改为login

把官方例子的Js代码改为Ts代码调用注册:

import { _decorator, Component, EditBox, Node } from 'cc';
import { encode,decode } from "./msgpack-ts-master/src"
const { ccclass, property } = _decorator;const _RECV_PAYLOAD_LENGTH = 1
const _RECV_PAYLOAD = 2const CLIENTID_LENGTH = 16
const ENTITYID_LENGTH = 16
const SIZE_FIELD_SIZE = 4const MT_INVALID = 0
// Server Messages
const MT_SET_GAME_ID = 1
const MT_SET_GATE_ID = 2
const MT_NOTIFY_CREATE_ENTITY = 3
const MT_NOTIFY_DESTROY_ENTITY = 4
const MT_DECLARE_SERVICE = 5
const MT_UNDECLARE_SERVICE = 6
const MT_CALL_ENTITY_METHOD = 7
const MT_CREATE_ENTITY_ANYWHERE = 8
const MT_LOAD_ENTITY_ANYWHERE = 9
const MT_NOTIFY_CLIENT_CONNECTED = 10
const MT_NOTIFY_CLIENT_DISCONNECTED = 11
const MT_CALL_ENTITY_METHOD_FROM_CLIENT = 12
const MT_SYNC_POSITION_YAW_FROM_CLIENT = 13
const MT_NOTIFY_ALL_GAMES_CONNECTED = 14
const MT_NOTIFY_GATE_DISCONNECTED = 15
const MT_START_FREEZE_GAME = 16
const MT_START_FREEZE_GAME_ACK = 17
// Message types for migrating
const MT_MIGRATE_REQUEST = 18
const MT_REAL_MIGRATE = 19const MT_GATE_SERVICE_MSG_TYPE_START = 1000
const MT_REDIRECT_TO_GATEPROXY_MSG_TYPE_START = 1001 // messages that should be redirected to client proxy
const MT_CREATE_ENTITY_ON_CLIENT = 1002
const MT_DESTROY_ENTITY_ON_CLIENT = 1003
const MT_NOTIFY_MAP_ATTR_CHANGE_ON_CLIENT = 1004
const MT_NOTIFY_MAP_ATTR_DEL_ON_CLIENT = 1005
const MT_NOTIFY_LIST_ATTR_CHANGE_ON_CLIENT = 1006
const MT_NOTIFY_LIST_ATTR_POP_ON_CLIENT = 1007
const MT_NOTIFY_LIST_ATTR_APPEND_ON_CLIENT = 1008
const MT_CALL_ENTITY_METHOD_ON_CLIENT = 1009
const MT_UPDATE_POSITION_ON_CLIENT = 1010
const MT_UPDATE_YAW_ON_CLIENT = 1011
const MT_SET_CLIENTPROXY_FILTER_PROP = 1012
const MT_CLEAR_CLIENTPROXY_FILTER_PROPS = 1013// add more ...const MT_REDIRECT_TO_GATEPROXY_MSG_TYPE_STOP = 1500const MT_CALL_FILTERED_CLIENTS = 1501
const MT_SYNC_POSITION_YAW_ON_CLIENTS = 1502// add more ...const MT_GATE_SERVICE_MSG_TYPE_STOP = 2000
export class ClientEntity extends Component {create(owner, typeName, entityID, attrs) {this.owner = owner this.typeName = typeNamethis.ID = entityIDthis.attrs = attrs this.isPlayer = false}onCreated() {if (this.typeName == "Account") {console.log("Account created, start logining...")}}    onBecomePlayer() {let scene = cc.director.getScene()console.log("获得玩家对象:", this, scene.name)if (this.typeName == "Avatar") {// 玩家登录成功!this.getGoWorld().onAvatarLoginSuccess( this )} else if (this.typeName == "Account") {// 账号创建成功,可以开始登陆if (scene.name != "login") {cc.director.loadScene("login");}}}onCall(method, args) {console.log(this.toString()+"."+method+"("+args+")")this[method](...args)}// Put Client Methods Here ShowError(msg) {// this.getGoWorld().showErrorTip(msg)console.log('ShowError'+msg);}
}
@ccclass('NewComponent')
export class NewComponent extends Component {ID="ID_TEST";start() {this.recvBuf = new ArrayBuffer()this.recvStatus = _RECV_PAYLOAD_LENGTHthis.recvPayloadLen = 0this.entities = {}this.sendBuf = new ArrayBuffer(1024*1024);this._sendPacket = new DataView(this.sendBuf);this._sendPacketWritePos = SIZE_FIELD_SIZEthis.connect();}update(deltaTime: number) {}onBtnClick(event, customEventData){var editBox = this.node.getChildByName("EditBox").getComponent(EditBox);console.log('按钮按下'+editBox.textLabel.string);let account = this.getEntityByType("Account")this.callServer(account,"Register",editBox.textLabel.string,"asdf");}    getEntityByType(typeName) {for (var eid in this.entities) {let e = this.entities[eid]if (e.typeName == typeName) {return e}}return null }callServer(entity:ClientEntity, method ) {var args = Array.prototype.slice.call(arguments);args = args.slice(2)this.callServerMethod( entity, method, args )}callServerMethod(entity:NewComponent, method:string, args) {console.log(">>> "+entity+"."+method+"("+args+")")// 	packet.AppendUint16(MT_CALL_ENTITY_METHOD_FROM_CLIENT)// 	packet.AppendEntityID(id)// 	packet.AppendVarStr(method)// 	packet.AppendArgs(args)this.appendUint16(MT_CALL_ENTITY_METHOD_FROM_CLIENT)this.appendEntityID(entity.ID)this.appendVarStr(method)this.appendArgs(args)this.sendPacket()}appendUint16(v) {this._sendPacket.setUint16(this._sendPacketWritePos, v, true)this._sendPacketWritePos += 2}appendEntityID(eid) {console.log(eid);let b = this.string2Uint8Array(eid)console.log("convert", eid, "to", b, b.length)this.appendBytes(b)}string2Uint8Array(str) {console.log(str);let bufView = new Uint8Array(str.length);for (var i=0, strLen=str.length; i<strLen; i++) {bufView[i] = str.charCodeAt(i);}return bufView;}appendBytes(b) {new Uint8Array(this._sendPacket.buffer, this._sendPacketWritePos, b.length).set(b, 0);  this._sendPacketWritePos += b.length}  appendVarStr(s) {let b = this.string2Uint8Array(s)this.appendVarBytes(b)}appendVarBytes(b) {this.appendUint32(b.length)this.appendBytes(b)}appendUint32(v) {this._sendPacket.setUint32(this._sendPacketWritePos, v, true)this._sendPacketWritePos += 4}    appendArgs(args) {console.log("appendArgs", args.length, args)this.appendUint16(args.length)for (var i=0; i<args.length;i++) {this.appendData(args[i])}}appendData(data) {data = encode(data)console.log("msgpack encode:", typeof(data), data.length)this.appendVarBytes(data)}sendPacket() {let payloadLen = this._sendPacketWritePos - SIZE_FIELD_SIZEthis._sendPacket.setUint32(0, payloadLen, true)let packetLen = this._sendPacketWritePosthis._sendPacketWritePos = SIZE_FIELD_SIZEconsole.log("sendPacket:", packetLen)this.websocket.send(this.sendBuf.slice(0, packetLen))}connect() {this.serverAddr='localhost';this.serverPort=15101;var serverAddr = 'ws://'+this.serverAddr+':'+this.serverPort+'/ws'console.log("正在连接 " + serverAddr + ' ...')var websocket = new WebSocket(serverAddr)this.websocket = websocketwebsocket.binaryType = 'arraybuffer'console.log(websocket)var gameclient = this//连接发生错误的回调方法websocket.onerror = function () {console.log("WebSocket连接发生错误");};//连接成功建立的回调方法websocket.onopen = function () {console.log("WebSocket连接成功");}//接收到消息的回调方法websocket.onmessage = function (event) {var data = event.dataconsole.log("收到数据:", typeof(data), data.length);gameclient.onRecvData(data)}//连接关闭的回调方法websocket.onclose = function () {console.log("WebSocket连接关闭");}//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。window.onbeforeunload = function () {console.log("onbeforeunload");}}onRecvData(data) {if (this.recvBuf.byteLength == 0) {this.recvBuf = data} else {var tmp = new Uint8Array( this.recvBuf.byteLength + data.byteLength );tmp.set( new Uint8Array( this.recvBuf ), 0 );tmp.set( new Uint8Array( data ), this.recvBuf.byteLength );this.recvBuf = tmp.buffer}console.log("未处理数据:", this.recvBuf.byteLength)while (true) {let payload = this.tryReceivePacket()if (payload !== null) {this.onReceivePacket(payload)} else {break }}}// 从已经收到的数据(recvBuf)里解析出数据包(Packet)tryReceivePacket() {let recvBufView = new DataView(this.recvBuf)if (this.recvStatus == _RECV_PAYLOAD_LENGTH) {if (this.recvBuf.byteLength < SIZE_FIELD_SIZE) {return null}this.recvPayloadLen = recvBufView.getUint32(0, true)console.log("数据包大小: ", this.recvPayloadLen)this.recvStatus = _RECV_PAYLOAD}// recv status == _RECV_PAYLOADconsole.log("包大小:", this.recvPayloadLen, "现有数据:", this.recvBuf.byteLength - SIZE_FIELD_SIZE)if (this.recvBuf.byteLength - SIZE_FIELD_SIZE < this.recvPayloadLen) {// payload not enoughreturn null}// 足够了,返回包数据var payload = this.recvBuf.slice(SIZE_FIELD_SIZE, SIZE_FIELD_SIZE+this.recvPayloadLen)this.recvBuf = this.recvBuf.slice(SIZE_FIELD_SIZE+this.recvPayloadLen)// 恢复到接收长度状态this.recvStatus = _RECV_PAYLOAD_LENGTHthis.recvPayloadLen = 0return payload}onReceivePacket (payload) {// payload is ArrayBufferpayload = new DataView(payload) // 转换为DataView便于操作var [msgtype, payload] = this.readUint16(payload)console.log("收到包:", payload, payload.byteLength, ",消息类型:", msgtype)if (msgtype != MT_CALL_FILTERED_CLIENTS && msgtype != MT_SYNC_POSITION_YAW_ON_CLIENTS) {var [dummy, payload] = this.readUint16(payload)console.log("gateid", dummy)var [dummy, payload] = this.readBytes(payload, CLIENTID_LENGTH) // read ClientIDconsole.log("clientid", dummy.length)}if (msgtype == MT_CREATE_ENTITY_ON_CLIENT) {this.handleCreateEntityOnClient(payload)} else if (msgtype == MT_CALL_ENTITY_METHOD_ON_CLIENT) {this.handleCallEntityMethodOnClient(payload)} else if (msgtype == MT_DESTROY_ENTITY_ON_CLIENT) {this.handleDestroyEntityOnClient(payload)} else if (msgtype == MT_CALL_FILTERED_CLIENTS) {this.handleCallFilteredClients(payload)} else if (msgtype == MT_NOTIFY_MAP_ATTR_CHANGE_ON_CLIENT) {this.handleNotifyMapAttrChangeOnClient(payload)} else if (msgtype == MT_NOTIFY_MAP_ATTR_DEL_ON_CLIENT) {} else if (msgtype == MT_NOTIFY_LIST_ATTR_APPEND_ON_CLIENT) {} else if (msgtype == MT_NOTIFY_LIST_ATTR_CHANGE_ON_CLIENT) {} else if (msgtype == MT_NOTIFY_LIST_ATTR_POP_ON_CLIENT) {} else {console.log("无法识别的消息类型:"+msgtype)}}readUint8(buf) {let v = buf.getUint8(0)return [v, new DataView(buf.buffer, buf.byteOffset+1)]}readUint16(buf) {let v = buf.getUint16(0, true)return [v, new DataView(buf.buffer, buf.byteOffset+2)]}readUint32(buf) {let v = buf.getUint32(0, true)return [v, new DataView(buf.buffer, buf.byteOffset+4)]}readFloat32(buf) {let v = buf.getFloat32(0, true)return [v, new DataView(buf.buffer, buf.byteOffset+4)]}readBytes(buf, length) {let v = new Uint8Array(buf.buffer, buf.byteOffset, length)return [v, new DataView(buf.buffer, buf.byteOffset+length)]}readVarBytes(buf) {var [n, buf] = this.readUint32(buf)var [b, buf] = this.readBytes(buf, n)console.log('VarBytes len', n, 'b', b.length)return [b, buf]}readEntityID(buf) {var [eid, buf] = this.readBytes(buf, ENTITYID_LENGTH)eid = this.uint8Array2String(eid)return [eid, buf]}readVarStr(buf) {var [b, buf] = this.readVarBytes(buf)let s = this.uint8Array2String(b)return [s, buf]}readBool(buf) {var b[b, buf] = this.readUint8(buf)b = b == 0 ? false : truereturn [b, buf]}readData(buf) {var [b, buf] = this.readVarBytes(buf)let data = decode(b)return [data, buf]}readArgs(buf) {var [argcount, buf] = this.readUint16(buf)console.log("readArgs: argcount", argcount)var args = new Array(argcount)for (var i = 0; i<argcount; i++) {var [data, buf] = this.readData(buf)args[i] = data}return [args, buf]}handleCreateEntityOnClient(payload) {var [isPlayer, payload] = this.readBool(payload)var [eid, payload] = this.readEntityID(payload)var [typeName, payload] = this.readVarStr(payload)var [x, payload] = this.readFloat32(payload)var [y, payload] = this.readFloat32(payload)var [z, payload] = this.readFloat32(payload)var [yaw, payload] = this.readFloat32(payload)var [clientData,payload] = this.readVarBytes(payload)clientData = decode(clientData)console.log("MT_CREATE_ENTITY_ON_CLIENT", "isPlayer", isPlayer, 'eid', eid,"typeName", typeName, 'position', x, y, z, 'yaw', yaw, 'clientData', JSON.stringify(clientData))var e = new ClientEntity()e.create( this, typeName, eid, clientData )this.entities[eid] = eif (isPlayer) {e.isPlayer = trueif (this.player) {// dupliate player!!!console.error("玩家对象重复:老玩家"+this.player.toString() + ",新玩家:", e.toString())}this.player = e}this.onEntityCreated(e)e.onCreated()if (this.player === e) {e.onBecomePlayer()}
}
onEntityCreated(e) {console.log("entity created:", e.toString())
}uint8Array2String(b) {return String.fromCharCode.apply(null, b)
}handleDestroyEntityOnClient(payload) {// typeName := packet.ReadVarStr()// entityID := packet.ReadEntityID()var [typeName, payload] = this.readVarStr(payload)var [entityID, payload] = this.readEntityID(payload)let e = this.entities[entityID]if (e == undefined) {return }delete this.entities[entityID]if (this.player === e) {this.player = nullconsole.log("失去玩家对象:", e.toString()) }
}handleCallEntityMethodOnClient(payload) {        // entityID := packet.ReadEntityID()// method := packet.ReadVarStr()// args := packet.ReadArgs()var [entityID, payload] = this.readEntityID(payload)var [method, payload] = this.readVarStr(payload)var [args, payload] = this.readArgs(payload)console.log("MT_CALL_ENTITY_METHOD_ON_CLIENT", "entityID", entityID, "method", method, "args", args.length, args)let e = this.entities[entityID]if (e == undefined) {console.log("找不到entity:", entityID)return }e.onCall( method, args )
}handleCallFilteredClients(payload) {var [fkey, payload] = this.readVarStr(payload)var [fval, payload] = this.readVarStr(payload)var [method, payload] = this.readVarStr(payload)var [args, payload] = this.readArgs(payload)console.log("MT_CALL_FILTERED_CLIENTS", fkey, "=", fval, "method=", method, "args=", args)this.player.onCall( method, args )
}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/143132.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB中norm函数用法

目录 语法 说明 示例 向量模 向量的 1-范数 两个点之间的欧几里德距离 矩阵的 2-范数 N 维数组的 Frobenius 范数 常规向量范数 norm函数的功能是计算向量范数和矩阵范数。 语法 n norm(v) n norm(v,p) n norm(X) n norm(X,p) n norm(X,"fro") 说明…

Python二级 每周练习题20

练习一: 日期计算器 设计一款日期计算程序&#xff0c;能否实现下面的功能&#xff1a; (1)要求用户分别输入年、月、日&#xff08;分三次输入&#xff09;&#xff1b; (2)程序自动会根据输入的年月日计算出这一天是这一年的第几天&#xff1b; (3)输出格式为&#xff1a;这…

Tomcat基础与优化

Tomcat介绍 Tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;Tomcat具有处理HTML页面的功能&#xff0c;通常作为一个Servlet和JSP容器&#xff0c;单独运行…

上网行为监管软件(上网行为管理软件通常具有哪些功能)

在我们的日常生活中&#xff0c;互联网已经成为了我们获取信息、交流思想、进行工作和娱乐的重要平台。然而&#xff0c;随着互联网的普及和使用&#xff0c;网络安全问题也日益突出&#xff0c;尤其是个人隐私保护和网络行为的规范。在这个背景下&#xff0c;上网行为审计软件…

蜜雪冰城涨价怒赞无数 雪王张红超卷出一条阳道

作者&#xff1a;积溪 简评&#xff1a;最近雪王涨价一元登上了热搜&#xff0c;但评论区却是一片和谐&#xff0c;雪王的魅力究竟是如何养成的&#xff1f;#蜜雪冰城 #雪王 #张红超 #奶茶 别的品牌涨价&#xff0c;只有吐槽声一片&#xff1b;但它涨价&#xff0c;却是网友们…

携手低代码平台公司,创造高效率办公!

当前&#xff0c;什么样的平台产品可以实现高效率办公&#xff1f;随着社会化发展程度的推进发展&#xff0c;很多客户朋友希望找到更理想的平台产品&#xff0c;助力企业管理好内部数据&#xff0c;起到链接互通各部门沟通桥梁的作用。低代码开发平台就是其中一种理想的得力助…

如何看待著名游戏引擎 Unity 宣布将更改收费模式,收取「运行时费用」?这将造成哪些影响?

先下结论&#xff1a;Unity 的高管是不是【不友善内容&#xff0c;请于 24 小时内及时更改】&#xff1f; 简单介绍下这个收费模式&#xff1a;年收入大于 20w 美金且安装量大于 20w 的&#xff0c;每一份额外下载需要给 Unity 交 0.2 刀。 首先&#xff1a;听上去好像不会影响…

使用显著性检测的可见光和红外图像的两尺度图像融合(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

树结构数据在table中回显 treeselect disabled

<el-table-column label"产业认定" align"center" prop"industryIdentification"><template slot-scope"scope"><treeselectv-if"scope.row.industryIdentification"v-model"scope.row.industryIdentif…

你听说过推挽电路吗?避免交越失真

推挽电路就是用两个三级管或者场效应管构成的放大电路&#xff0c;这个电路的特点就是输出电阻小&#xff0c;能够驱动大的负载&#xff0c;从而能够使得单片机管脚直接驱动发光二极管、蜂鸣器。上面的三极管是N型三极管&#xff0c;下面的三极管是P型三极管&#xff0c; 当输入…

机器人中的数值优化|【五】BFGS算法非凸/非光滑处理

机器人中的数值优化|【五】BFGS算法的非凸/非光滑处理 往期内容回顾 机器人中的数值优化|【一】数值优化基础 机器人中的数值优化|【二】最速下降法&#xff0c;可行牛顿法的python实现&#xff0c;以Rosenbrock function为例 机器人中的数值优化|【三】无约束优化&#xff0…

Redis的高可用——主从复制、哨兵模式、Redis群集部署

目录 Redis高可用 Redis主从复制 主从复制的作用 主从复制的流程 主从复制部署 Redis哨兵模式 哨兵的核心功能 哨兵模式的作用 哨兵结构的组成 哨兵模式故障转移机制 故障转移过程 哨兵模式部署 Redis群集 集群的作用 Redis集群的数据分片 Redis集群部署 Redi…

手把手教你用 Milvus 和 Towhee 搭建一个 AI 聊天机器人!

作为向量数据库的佼佼者&#xff0c;Milvus 适用于各种需要借助高效和可扩展向量搜索功能的 AI 应用。 举个例子&#xff0c;如果想要搭建一个负责聊天机器人数据管理流程&#xff0c;Milvus 必然是首选向量数据库。那么如何让这个应用程序开发变得易于管理及更好理解&#xff…

构建自动化测试环境:使用Docker和Selenium!

随着软件开发的日益复杂和迭代速度的加快&#xff0c;自动化测试被越来越广泛地应用于软件开发流程中。它能够提高测试效率、减少测试成本&#xff0c;并保证软件质量的稳定性。在构建自动化测试环境方面&#xff0c;Docker 和 Selenium 是两个非常有用的工具。下面将介绍如何使…

cocos2dx查看版本号的方法

打开文件&#xff1a;项目根目录\frameworks\cocos2d-x\docs\RELEASE_NOTES.md 知道引擎版本号的意义&#xff1a; 1.面试中经常被问到(面试官想知道你会不会查版本号&#xff0c;你会查也不一定会去看&#xff0c;如果你去看了说明你是一个有心人&#xff0c;或者想深入研究下…

linux使用操作[3]

文章目录 版权声明环境变量$符号自行设置环境变量 上传、下载rz、sz命令 压缩、解压tar命令压缩tar解压zip 命令压缩文件unzip 命令解压文件 版权声明 本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明&#xff0c;所有版权属于黑马程序员或相关权利人…

如何利用人才测评系统提升企业招聘效率

公司需要的是能产出价值的员工&#xff0c;但是要想找到完全符合条件的员工&#xff0c;其实并不容易&#xff0c;尽管应聘的人数很多&#xff0c;但不是跳槽的&#xff0c;就是转行的&#xff0c;要么就只能从应届生培养开始了。 从招聘流程上&#xff0c;以现在的模式&…

SoftwareTest3 - 要了人命的Bug

软件测试基础篇 一 . 如何合理的创建一个 Bug二 . Bug 等级2.1 崩溃2.2 严重2.3 一般2.4 次要 三 . Bug 的生命周期四 . 跟开发产生争执应该怎么解决 Hello , 大家好 , 又给大家带来新的专栏喽 ~ 这个专栏是专门为零基础小白从 0 到 1 了解软件测试基础理论设计的 , 虽然还不足…

Rabbit消息的可靠性

生产者重连 消费者重试 Confirm模式简介 消息的confirm确认机制&#xff0c;是指生产者投递消息后&#xff0c;到达了消息服务器Broker里面的exchange交换机&#xff0c;则会给生产者一个应答&#xff0c;生产者接收到应答&#xff0c;用来确定这条消息是否正常的发送到Broker…

适合企业的跨隔离网文件摆渡的四种方式及优缺点

在当今的信息化时代&#xff0c;数据是企业的重要资产&#xff0c;如何有效地管理和传输数据&#xff0c;尤其是跨隔离网的大文件数据&#xff0c;是企业面临的一个挑战。跨隔离网文件摆渡是指在不同安全等级或网络环境的计算机之间传输文件的过程。 目前&#xff0c;市场上常见…