在游戏的开发过程中,前期的规划 往往比 后期的“优化”更为重要!比如多分辨率适配,如果前期没有规划好,可能导致的情况是,画面只在当前测试开发机或者一部分机型正常显示。做了多套资源适配,可以使在合适的机型使用对应的图片资源,避免在高清屏幕使用低质量的图片,在低分辨率屏幕因为图片太大而浪费硬件资源。机制与策略分离,可以让你设计出简单有效的接口。模块化的设计可以让你组织好各种逻辑流程,条理分明 ~ 前期的规划工作可以有很多,一叶也在摸索之中,以使游戏的开发尽量变的简单灵活且可控。最简单的也是最容易忽略的地方,跟我们打交道最多的要数精灵了,从图片创建一个精灵,很简单的开端,将以此展开行动 ~
本文使用 Cocos2d-3.0alpha1 版本,创建了一个 C++ 项目,介绍在 C++ 中,如何处理资源相关的内容,如果读者使用脚本,也可以参考本文中资源管理理念而忽略语言特性,你可以在Github1 上面看到本文所有源码。(注: 本博文略有更改)
名字系统
也许你可称之为 “命名规范”,但显然它无法表达我所想说的内容,很多人在创建精灵的时候喜欢直接使用资源名称,而没有任何定义,这是一个不好的习惯,如果游戏资源不存在,缺少,或者修改名字,如此你需要在多出引用的地方一一修改。游戏开发中的变数总是无法预料,合理的“名字系统”可以节省很多人力。
我们设定一个文件,这里名为 “Resources.h” 的文件,在其中定义所有的资源名称,在游戏开发中,尽量只 使用此处的名称,如图片名称,字体名称,声音资源等。这样做有以下好处,只是简单说几点:
- 如果对资源做出修改,我们可以修改此处定义,以保证同步,避免缺失,命名错误,错误引用等问题
- 在图片名定义修改时,编译器会编译出错,并自动帮助我们 “找” 出引用的地方,方便修改
- 由于有常量定义的缘故,我们的 IDE 会自动补全所有以定义变量名称,减少出错的可能,提高效率
使用脚本来自动生成文件常量定义显然是个行之有效的途径,这种机械式的操作交给脚本就行了,它总能出色的完成任务,首先来看看项目的 Resources 目录内容:
以上是资源文件,那么通过脚本所生成的 “Resources.h” 文件又是什么样子的呢,脚本在 Github 仓库中可以找到(注意:资源名中最好不要有空格,以免留下“隐患”):
#ifndef cocos2dx_resource_Resources_h
#define cocos2dx_resource_Resources_h
#include <iostream>
#include <vector>
#include "cocos2d.h"
using namespace std;
//using namespace std;
// search paths
//static const std::vector<std::string> searchPaths = {
// "fonts",
// "images",
//};
// files
static const char si_CloseNormal[] = "CloseNormal.png";
static const char si_CloseSelected[] = "CloseSelected.png";
static const char sjs_file_list[] = "file_list.json";
static const char si_HelloWorld[] = "HelloWorld.png";
static const char st_MarkerFelt[] = "Marker Felt.ttf";
static const char sp_ghosts[] = "ghosts.plist";
static const char si_ghosts[] = "ghosts.png";
static const char sp_grossini_family[] = "grossini_family.plist";
static const char si_grossini_family[] = "grossini_family.png";
static const char si_JungleLeft[] = "JungleLeft.png";
// texture //
// ghosts.plist
static const char si_child1[] = "child1.gif";
static const char si_father[] = "father.gif";
static const char si_sister1[] = "sister1.gif";
static const char si_sister2[] = "sister2.gif";
// grossini_family.plist
static const char si_grossini[] = "grossini.png";
static const char si_grossinis_sister1[] = "grossinis_sister1.png";
static const char si_grossinis_sister2[] = "grossinis_sister2.png";
// json key
static const char file_name[] = "file_name";
static const char file_index[] = "file_index";
static const char texture_name[] = "texture_name";
static const char texture_plist[] = "texture_plist";
static const char texture_image[] = "texture_image";
#endif
看到通过脚本,我们生成了所有文件的常量定义,这让得我们可以在游戏中任意使用,但是请注意,这里生成的文件名称是没有包含路径的,所以在定义文件之前,也自动生成了目录列表 searchPaths,顾名思义,设定了一个目录列表,以便找寻资源,我们可以在程序的开始处使用 :
CCFileUtils::sharedFileUtils()->setSearchPaths( searchPaths );
来设定游戏的资源目录列表,这样我们就可以不用关心资源所在的目录了,你甚至可以根据需要合理的调整资源目录。
AppDelegate.cpp
const char* directorys[] =
{
"fonts", "images"
};
std::vector<std::string> searchPaths;
for ( int i = sizeof(directorys)/sizeof(directorys[0])-1; i >= 0; i-- )
{
searchPaths.push_back(directorys[i]);
}
CCFileUtils::sharedFileUtils()->setSearchPaths( searchPaths );
注意:通过设置 searchPaths 可以让我们不用关系资源的路径所在,那么意味着资源名称必须唯一,否则可能会出现引用问题。其次,是如果使用了多套资源方案,请注意 searchPaths 的先后顺序关系。本文暂不考虑多套资源。关于忽略资源目录的做法,如果有不同看法者,欢迎留言讨论,对我来说,忽略路径是利大于弊的 ~
以上通过脚本自动生成了文件列表,但是这显然不够,我们看到资源当中有两张 打包 资源图片(可以使用 TexturePacker 对图片资源进行打包,具有占用更小空间,优化运行效率等诸多好处,后面还会介绍此点) plist 文件。我们当然也是需要使用打包中资源的,所以脚本需要能够自动解析 plist 文件,并提取出 TexturePacker 打包的资源名称,请看如下定义,同样是自动生成在 “Resources.h” 文件之中:
// ghosts.plist
static const char si_child1[] = "child1.gif";
static const char si_father[] = "father.gif";
static const char si_sister1[] = "sister1.gif";
static const char si_sister2[] = "sister2.gif";
// grossini_family.plist
static const char si_grossini[] = "grossini.png";
static const char si_grossinis_sister1[] = "grossinis_sister1.png";
此时我们就能用以下代码来创建精灵了,都引用了资源名称定义,并且使用两种方式创建了精灵:
//直接由图片创建精灵
CCSprite * hello = CCSprite::create(si_ghosts);
hello->setPosition(ccp(size.width/2,size.height/2));
this->addChild(hello);
// 从打包资源创建精灵
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile(sp_grossini_family, si_grossini_family);
CCSprite * sprite = CCSprite::createWithSpriteFrameName(si_grossinis_sister1);
sprite->setPosition(ccp(size.width/2,size.height/2));
this->addChild(sprite);
上面我们使用两种方式创建精灵,为什么会有两种方式?也许你可以看看
『子龙山人』 翻译的文章
『在cocos2d里面如何使用Texture Packer和像素格式来优化spritesheet』其中详细的介绍了图片资源打包优化的相关细节问题,一个游戏最多的就是图片资源,优化空间最大的也是图片资源,里面详细的介绍了优化图片资源占用空间 50% 以上,如何使游戏运行内存占用优化近 50%,以 cocos2d 为例,但 cocos2d-x 同样能够适用,而且能通过脚本自动打包。 所以合理的对图片资源进行打包优化是非常有必要的。但如何处理这个流程确实不好定夺,因为不同资源的使用方式不同,因为这两种方式的存在,导致我们编写代码的逻辑不同,这需要提前预定好,所以我们考虑如下开发流程:
在游戏开发前,对所有资源打包后提供给 编写游戏人员,也就是说在写程序之前,游戏资源就已确定,那些以打包,哪些未打包都已经知道,如前面一样,通过两种方式创建精灵。但是这样的结果是,前期规定好了的,后期就无法改动,或者说很难改动,牵一发而动全身啊 ~ 这就需要加大 前期的规划 力度,以确保后期不会出现太大太多事与愿违的情形。这种情况下的 后期优化 将会非常蹩脚。况且加大前期规划的力度,可能会对整个项目的进程有所影响,如比编写人员的动工会稍缓,人力资源分配不合理。
有关图片资源类型的“透明”处理,可参考http://blog.leafsoar.com/archives/2013/11-27.html
若是使用脚本的朋友,实现方式类似的。