欢迎参与讨论,转载请注明出处。
前言
在游戏开发的领域里,游戏资源的管理可谓一个很重要的基础功能,在一些强大的游戏引擎会为其配备一套解决方案。而LÖVE很不幸的再次没有提供,好在即使没有也起码做好了内存管理的工作,那么即便自己动手做一套也不是什么困难的事了,本文便记录其中心得。
资源管理模块本质上只做了两件事:
- 生命周期管理:保证多次加载时资源的复用,在无需该资源时进行销毁。
- 配置接口:提供加载资源的API,以及外部化的资源配置。
接下来便围绕以上两点展开说明其中的要点。
生命周期管理
如上文所言,生命周期管理要做的事即:保证多次加载时资源的复用,在无需该资源时进行销毁。资源复用的实现思路非常的简单,使用哈希表将资源以路径-对象为映射关系进行存储即可,然后每次加载资源时进行一次检查,若存在表内则直接获取,否则再进行读取。
|
|
接下来是第二个问题:在无需该资源时进行销毁。说得具体点便是:当外部没有对象引用该资源时,将其从资源池里移除。如此只需要使用弱引用即可,在Lua里即是建立弱表(weak table)。
|
|
如此当poor内存在外部无引用的对象时,在垃圾回收时便会将其移除。如此资源的生命周期管理便算完成了。
配置接口
资源文件按照性质可以划分为两种:数据文件(二进制为主,如图片、声音等),配置文件(可编辑、可序列化的变量对象)。对于配置文件,Lua可以很方便地直接使用本体:
|
|
只要将其读取后使用loadstring(text)()
函数便可将其序列化,在其他引擎也有自定义配置格式以编辑器加持的形式解决,如Unity。现实情况中,一般数据文件会通过配置文件进行加载,即在配置文件提供对应资源的路径,然后由代码进行加载处理。
|
|
如上配置所示,此配置的image项将会由代码根据配置提供的路径glow
进行读取对应目录下的image/glow.png
文件。如此便可看出,资源与资源之间存在很强的联动性,它们就像是一棵树,节节相扣。对于那些较上层的配置文件而言,往往会从上到下牵涉巨多资源。这么做是很棒的,一加载便将所有相关的资源都加载了,只要在恰当的场合进行资源加载(如切换地图),核心游戏过程中则几乎不会涉及到加载了。
配置的健壮性
在没有编辑器加持的情况下,单纯的配置文件健壮性是有限的,最突出的两个需求便是:
- 快捷定位路径:如位于
sprite/test/1.cfg
的配置文件想要读取位于同路径、不同分类下的image/test/1.png
文件,如果没有一些辅助手段,那么只能傻傻的输入全路径,十分愚蠢。 - 参数注入:倘若存在一些大体相似,少部分不同的配置需求,若没有参数注入,那么只好傻傻的批量复制修改,也是十分的愚蠢。
当然以上两点若是存在编辑器,自然可以无视并通过自动化手段之类达到相同的效果。但目前项目暂无编辑器,于是采用了替换文本的方案。
|
|
如上配置所示,$A
便是代表当前资源分类下的路径,即替换为test/1
,如此便可快速定位至image/test/1.png
,算是一种语法糖吧。至于$1 $2
则代表第1、第2个注入的参数,在调用的API的时候会以{1.2, 1}
的形式作为参数填入。如此便会将$1
替换为1.2
,同理$2
替换为1
。当然这种注入了参数的配置在资源池的key是不能使用路径的(不是标准的),会在其后加入参数值成为:test/1|1.2|1
。
配置的只读性
由于资源对象往往都是独一一份,到处引用,倘若哪处不小心对其进行了修改,那么便会引起连锁反应影响全局。所以有必要考虑将资源对象设置为只读的:
|
|
只要将对象拿去处理后,试图修改该对象时将会报错。当然这样做是有代价的:pairs()
和table.getn
函数将会变得无法直接使用,需要取出其元表方可使用。所以需要配备专门函数:
|
|
这样子使用起来虽然麻烦了点,不过的确将资源对象和一般对象作出了明显的区分。另外只读处理的时机也需要考量的,一般得在整个资源对象处理完毕后才进行。
配置的个性化
对于一些普遍的资源文件(图片、声音、精灵、动画等),一般配备专属的处理函数即可。但是到了业务层面情况往往会繁杂许多,将会存在许多个性化的配置格式。这时候便需要将业务对象和资源对象进行绑定:
|
|
如上配置所示,这是一个哥布林的投掷状态,这里的配置便需要提供子弹资源以及发射坐标了。关于这些个性化的配置项,将会如此解决:
|
|
可以看到,通过配置的script项找到对应的脚本业务对象,然后调用其对象的HandleData函数进行解析。如此便解决了个性化的问题。
后记
还是如上篇一般,这个问题对于流行的大引擎而言已经提供了成熟的解决方案。上了贼船呀,只能走到黑了。不过造造轮子也是有益技术的提升的。