如无必要,勿增实体。
一句话定义
游戏框架,是在引擎和脚本语言的基础上,对一些常用方法的二次封装,使其更适合个人或团队使用。
更为适当的描述是「脚手架」。但不必搞特殊,既然大家都说成是「框架」,那就用框架就好。
从一个例子出发
在creator(版本2.4.0,下同)中,有一个用于动态加载资源的api:cc.resources.load。参考文档为https://docs.cocos.com/creator/manual/zh/scripting/load-assets.html#%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD-resources,在文档中可以看出,此api需要传入一个回调方法,用于接收载入成功后的资源处理。那么如果我并不习惯于回调,而是习惯于使用async方法,那么就可以做如下修改:
// framework.ts
export async function load_async<P extends string | string[], T extends typeof cc.Asset>(
paths: P,
type: T,
): Promise<P extends string ? InstanceType<T> : InstanceType<T>[]> {
return new Promise(res => {
cc.resources.load(paths, type, (error, assets) => {
error ? res() : res(assets as any);
});
});
}
封装之后,可以使用load_async方法代替cc.resources.load方法,即可以返回一个Promise,又有完备的类型提示。
这是对游戏引擎用法的二次封装,还有一些在脚本语言层面的二次封装:
// framework.ts
...
// 用法,求一个数的正数模,比如-1%10=-1,其正数模为9
export function get_positive_mode(n: number, mode: number): number {
return ((n % mode) + mode) % mode;
}
// 类型用法,通过is关键字将x锁定为string类型
export function is_string(x: unknown): x is string {
return typeof x === "string";
}
难点在于抽象过程
对于编程工作而言,设计难度高于实现难度,好的设计更是可以简化实现过程。
对于游戏框架也是如此,其抽象过程分为2个方面:
- 对外,需要考虑那些功能是需要在框架层实现的,那些功能是可以在游戏内容层实现的。
- 对内,需要考虑如何取舍模块,如何设计模块,如何减少模块内部耦合。
这需要大量的经验。我个人总结的相关经验,可以参考我的其他文章。
实现路线选择:内建,插件
这都是我自己起的名字,以一个表格做分区:
内建 | 插件 | |
---|---|---|
含义 | 将框架代码放到assets目录下,使用时直接调用 | 在单独一个项目中实现框架,打包为umd包后放入assets下作为插件使用 |
特点 | - 方便理解和修改。 - 可以有一些基于ccclass的脚本。 - 可以有一些游戏资源等。 |
- 可以使用最新版的typescript。(之前creator所使用的typescript版本都比较落后,但是在2.4.0版本中的typescript版本已经更新到3.9.2) - 可以统一更新。因为框架层和内容层已经完全分开,可以通过替换文件的方式来更新。 |
使用场景 | - 框架层不稳定,还需要频繁修改。 |
- 需要使用新的typescript特性。 - 多项目同时开发,共用同一个框架。 |
随着creator2.4.0的发布,应该还有第3种路线:使用Asset Bundle。不过我已不再进行游戏开发工作,所以也就并未再进行研究。
实际实现过程中,可以都使用,发挥各自的优点。比如在一个框架项目中编写底层代码,在一个示例项目中配置开发环境,添加基于ccclass的脚本,添加常用游戏资源等。
一个示例项目
我进行了近2年的游戏开发工作,维护了一个个人使用的框架项目。
https://github.com/fkworld/cocos-game-framework
但我已经转行,此项目也不再主力维护。希望大家多多star,助我找到新工作~
至于直接使用,我认为,游戏框架应该是基于个人的编码习惯、项目的实际需求来进行编写。我这里只是作为一个范例,尤其是打包过程。希望大家在自己的工作中不断总结,不断交流,写出适合自己的框架项目。