从微信小游戏出生(2018年初)到现在,已经使用CocosCreator一年多了(之前做手游主要是cocos2d-x+lua),趁着这段时间有空,想着整理出一个基于CocosCreator游戏开发框架。
这个框架主要是将我在开发过程中觉得好用的结构和模式、插件,以及在论坛上和博客上参考大神们的教程和想法(有些是直接拿来用),整合在一起的。一方面想提高开发效率,另一方面大家发现问题及时提出及时讨论,慢慢优化和改进。
框架工程是否跟游戏工程分开
在开始之前,因为这个问题纠结了好久。框架工程跟游戏工程分离,框架工程作为一个独立代码库,仅仅作为游戏工程的子模块,这样代码库可以统一管理。
我上个项目就是这样做的,但是你会发现,把框架模块单独拿出来给下一个项目复用,里面有一些耦合了游戏工程的代码。如果要每个成员在紧张的游戏开发过程中保持清醒,去保持框架模块的独立性,肯定是一个蛋疼的过程。
后来阅读了两篇文章(关于游戏设计模式的)之后,确定了该框架仅仅作为一个“纯净”的基础框架,每开一个新项目就拷贝过去,然后根据游戏的需求自己去调整。
两篇文章的链接在下面,有兴趣的可以了解下,不扯远了,准备进入正题!
【游戏设计模式】之一 序言:架构,性能与游戏
为什么在游戏开发中我不喜欢用MVC系列模式了
为什么有些资源放在resources里面,有些放到外面?
总结一下:
1、resources文件夹中的资源可以跟它外部的其他资源,相互引用,所以放哪,问题不大。
2、只有放在resources文件夹的资源才能用cc.loader.loadRes动态加载。
3、构建时,resources文件夹中的所有资源连同它们关联依赖的resources文件夹外部的资源,都会被导出,并且项目中无用的资源将会在构建的过程中自动剔除。
4、resources文件夹的资源,会增大包体和settings.js的大小,JSON的自动合并策略也将受到影响,无法尽可能将零碎的JSON合并起来。
配置表模块
直接在现有的插件excel-killer的基础上做了小调整。
plugins-excel/excel
:存放excel表
plugins-excel/excel-ouput
: 存放执行插件后的js输出文件
assets/script/data/config
: 执行插件后,会自动把js文件从plugins-excel/excel-ouput拷贝到此目录
let cfgman = require('CfgMan');
console.log(cfgman[1].name); // 小明
数据模块
IDataModel.ts
:数据模块基类,主要功能:读取数据表、读写本地缓存数据、网络数据交互。
LoadStorage()
:将该模块的本地缓存数据读取到内存
Query(sKey: string, defaultValue: any = null)
:访问指定键名的值
Set(sKey: string, value: string | number)
:设置指定键名的值
Save()
:保存内存数据到缓存文件
sendProtocolMsg(msg)
:发送协议到服务端
registerListeners()
:注册网络监听事件,需要在getMessageListeners()
定义需要监听的协议和方法
// AccountModel.ts
getMessageListeners() {
return {
// key:消息名,value:执行函数
['G2C_Login']: (msg) => { this.G2C_LoginSuccess(msg) },
}
}
UI模块
UIMng
:UI管理器,用于打开、关闭UI
UIBase
:UI界面基类,在这里可以定义一些通用方法,供子类调用或者继承
UIHelp
:UI工具类,封装一系列UI相关的功能方法
1、新建一个场景或者prefab
2、选中,然后到工具栏:扩展 -> ui-creator
create-node-tree操作
:将prefab节点树的结构自动导出到ts文件(目标文件夹:assets/script/data/autoui)
export default class auto_notice extends cc.Component {
const { ccclass } = cc._decorator;
@ccclass
export default class auto_notice extends cc.Component {
notice: cc.Node;
background: cc.Node;
title: cc.Node;
content: cc.Node;
btnClose: cc.Node;
public static URL:string = "db://assets/resources/prefab/notice/notice.prefab"
onLoad () {
this.notice = this.node
this.background = this.notice.getChildByName("background");
this.title = this.notice.getChildByName("title");
this.content = this.notice.getChildByName("content");
this.btnClose = this.notice.getChildByName("btnClose");
}
}
以后,你想要使用ui节点,就不需要各种getChildByName,或者搞个property在编辑器拖,所有的节点都导出在一个ts文件,然后作为一个组件添加到UI文件中,你只需要this.ui[节点名称]即可访问。
create-ui-template操作
:自动生成UI模板TS文件
UI模板在packages\ui-creator\core\ui-template.txt
中定义。
3、将第2步create-ui-template操作
生成的UI脚本文件,在编辑器拖到prefab的根节点作为组件
4、UI的基本操作都封装在UIHelp中
UIHelp.ShowUI(UINotice); // 打开ui
UIHelp.CloseUI(UINotice); // 关闭ui
UIHelp.SetLabel(this.ui.title, '测试公告标题'); // 修改label节点文本
后续补充关闭UI清除相关无用资源
1、MVC模式。在框架中,每一个功能创建一个model类继承IDataModel,用于处理数据(配置表、本地数据、网络数据)。新建的prefab就是view,挂载在prefab的脚本组件就是controller,在controller实现功能逻辑。
2、很多人在用CocosCreator开发时,经常会往节点上挂脚本,往按钮上绑定事件(看过好多github上的项目都是这样)。但我个人是很不建议这样做的,正常的团队开发合作中都是分工明确的,比如你在编辑器中用按钮绑定事件,难道美术修改的时候还要关心代码么。而且任意节点都可以挂脚本,这个真的有点“灾难性”,我要找在这个脚本在哪里用到的时候还要去编辑器一个个找么(可能有其他快速查找的方法,我不知道的,请指点一下)。
3、所以我是比较支持,能用代码实现的尽量用代码实现,脚本文件能挂在根节点(方便找)的尽量挂在根节点。
网络模块
数据协议用Protobufjs,网络协议用WebSocket
1、安装nodejs、npm
2、到新建的cocoscreator工程目录,初始化项目:执行npm init -y
3、安装protobufjs5.x版本:执行npm install --save-dev protobufjs@5
4、覆盖原protobuf的loadProtoFile方法
protobuf原来的loadProtoFile方法:
ProtoBuf.loadProtoFile = function(filename, callback, builder) {
if (callback && typeof callback === 'object')
builder = callback,
callback = null;
else if (!callback || typeof callback !== 'function')
callback = null;
if (callback)
return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"]+"/"+filename["file"], function(contents) {
if (contents === null) {
callback(Error("Failed to fetch file"));
return;
}
try {
callback(null, ProtoBuf.loadProto(contents, builder, filename));
} catch (e) {
callback(e);
}
});
var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename);
return contents === null ? null : ProtoBuf.loadProto(contents, builder, filename);
};
这里用了ProtoBuf.Util.fetch来读文件,所以需要重写loadProtoFile方法,用cc.loader.loadRes代替Util.fetch方法来读取文件:
let ProtoBuf = require('protobufjs');
ProtoBuf.Util.IS_NODE = cc.sys.isNative;
// 此方法是将ProtoBuf.Util.fetch函数替换成cc.loader.loadRes函数,以解决在微信小游戏中不能使用XHR的问题
ProtoBuf.loadProtoFile = function(filename, callback, builder) {
if (callback && typeof callback === 'object')
builder = callback,
callback = null;
else if (!callback || typeof callback !== 'function')
callback = null;
if (callback)
return cc.loader.loadRes(typeof filename === 'string' ? filename : filename["root"]+"/"+filename["file"], function(error, contents) {
if (contents === null) {
callback(Error("Failed to fetch file"));
return;
}
try {
callback(error, ProtoBuf.loadProto(contents, builder, filename));
} catch (e) {
callback(e);
}
});
var contents = cc.loader.loadRes(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename);
return contents === null ? null : ProtoBuf.loadProto(contents, builder, filename);
};
CocosCreator已经支持WebSocket,而如果是微信小游戏则用微信提供的WebSocket,具体查看:https://developers.weixin.qq.com/minigame/dev/api/network/websocket/wx.connectSocket.html
工程中两种都实现了,其中浏览器平台已经测试过可行,并且提供了Nodejs服务端工程。
ProtoBuf.ts
:对protobufjs的修改和封装
ProtoLoader.ts
:用于加载proto文件
Message.ts
:proto msg的基类,并将msg缓存起来
ProtoMessage.ts
:插件根据proto文件生成的代码
Socket.ts
:WebSocket/WxSocket的具体实现
SocketDelegate.ts
:Socket代理类,根据具体平台创建socket,提供统一回调接口供具体Socket调用
Network.ts
:网络的逻辑实现,跟用户打交道,连接网络、关闭网络、登录流程、断网、重连都可以在这里处理
将proto转成TS代码,在开发中会有编辑器智能提示。
日志模块
export const LOG_TAG = {
SOCKET: { desc: 'LOG_SOCKET', isOpen: true },
TEST: { desc: 'LOG_TEST', isOpen: false },
}
Log.log(LOG_TAG.SOCKET, 'socketprint');
Log.warn('warn');
Log.error('error');
log方法第一个参数为开关分类,warn和error没有,因为我认为一般调试打印用log方法就够了,如果你用warn或error,肯定是需要所有人都知道的。
事件模块
EventMng.ts
:事件分发我偷懒了,直接new一个cc.EventTarget来用,目前没有发现其他问题。
其他
GameController.ts
:游戏全局控制类,比较杂的不知道放哪的可以看看能不能放这里
GameDataCenter.ts
:管理游戏各个模块数据
global.d.ts
:用于扩展基础模块
utils文件夹
:用于存放一些工具类
这篇文章讲的主要是框架有什么东西,有些地方为何要这么设计,githu工程点这里。
看完有点懵逼?没关系,下篇文章是:CocosCreator游戏开发框架(二):怎么用。
我会在框架的基础上快速开发一个简单的客户端登陆系统,帮助大家快速上手。