打开src/cocos2d-x-3.14.1/tests/cpp-tests的目录可以看到有很多和平台相关的文件夹
比如android, android-studio, linux, ios, mac, win10, win32,
所以说只要游戏写一次,按照编译指令就会生成对应的文件,比如编译android会生成对应的apk,ubuntu运行的话会生成对应的可执行程序,在前面的章节中有介绍。
这里主要看两个文件夹proj.android 和proj.linux
和两个目录Classes目录和Resources目录,根据它们的定义就很容易知道Resources是放资源文件的,Classes是放源码目录的。
cocos2d-x使用的是cmake构建工程,cmake后面需要专门的章节进行介绍,
因为懂得怎么写cmake就知道怎么构建工程,对理解cocos2d-x引擎整体的构建起到举足轻重的作用。
来看proj.android文件夹
进入proj.android文件夹,就是一个最简单的android工程,java部分几乎没有什么实现,很多都是android自动生成的文件,有几个地方需要自己改动:
res/values/strings.xml文件下的
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">CppTests</string>
</resources>
这里可以更改生成apk的名字,也可以更改程序的显示图片,最直接的做法就是替换res/drawable-Idpi中的icon.png和它命名一样的名字。
AndroidManifest.xml规定了程序的入口
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
我们最需要改的是jin目录下的Android.mk文件
其中LOCAL_SRC_FILES是重中之重,规定自己编写的文件路径位置,LOCAL_C_INCLUDES规定头文件位置。
android是通过自身的ndk开发调用生成cpp_tests_shared库的,这里就不讲ndk怎么实现,语言的本身只是更方便。
其实android系统的实现都是用的C++和C,通过java提供的jni功能完成所有的到java的转换。
app层用java可以更方便开发者快速有效地开发功能比较好的程序,而不需要关注底层的实现,需要明确注意client和implementation的身份。
说了一大段的废话,其实最重要的是上面标红色的部分。
最后再说一句它是用ant打包的。
再来看proj.linux构建linux程序
这个更简单只有一个main.cpp文件
#include "../Classes/AppDelegate.h"
#include "cocos2d.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string>
USING_NS_CC;
int main(int argc, char **argv)
{
// create the application instance
AppDelegate app;
return Application::getInstance()->run();
}
在这里创建了一个类,就像冰山里的一角,下面蕴藏着巨大的密码。
return Application::getInstance()->run();
让程序一直进入循环中,接收外部的点击事件,分发事件,所以这个run()会在程序结束返回。
重点关注CMakeLists.txt文件, 它定义了一个变量TEST_SRC表示自己写的文件夹。
关于文件的部分已经讲完了,接着分析Classes文件夹中的第一个类AppDelegate.cpp,万事开头难,后面就轻车熟路了。
AppDelegate.h
可以看到这个类定义了一个默认构造器,定义了一个析构器,
initGLContextAttrs
根据函数名大概可以知道初始化opengl相关的属性的,具体是操作什么还得看源码,后面的VisibleSize什么的都与它相关。
applicationDidFinishLaunching, applicationDidEnterBackground, applicationWillEnterForground根据它们的字面意思和注释就很容易知道干什么。
程序启动之后做什么,程序进入背景怎么做,程序进入前景(用户可以看到的界面)做什么,在AppDelegate.cpp文件中都有说明。
有一个_testController变量,
#ifndef _APP_DELEGATE_H_
#define _APP_DELEGATE_H_
#include "cocos2d.h"
class TestController;
/**
@brief The cocos2d Application.
Private inheritance here hides part of interface from Director.
*/
class AppDelegate : private cocos2d::Application
{
public:
AppDelegate();
virtual ~AppDelegate();
virtual void initGLContextAttrs();
/**
@brief Implement Director and cocos2d::Scene* init code here.
@return true Initialize success, app continue.
@return false Initialize failed, app terminate.
*/
virtual bool applicationDidFinishLaunching();
/**
@brief Called when the application moves to the background
@param the pointer of the application
*/
virtual void applicationDidEnterBackground();
/**
@brief Called when the application reenters the foreground
@param the pointer of the application
*/
virtual void applicationWillEnterForeground();
private:
TestController* _testController;
};
#endif // _APP_DELEGATE_H_
AppDelegate.cpp文件
构造器设置_testController为空,析构一个SimpleAudioEngine 和一个ArmureDataManager,
AudioEngine是和声音有关的,暂时不要去管它,等学到声音相关的再去看。
ArmureDataManager这个类的注释是这样的
@brief format and manage armature configuration and armature animation
这个应该是和cocostudio相关的,不过现在官方不提倡使用cocostudio,提倡使用cocos creator,不过这里使用的是qt creator,一个很不错的c++ gui的ide.
GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8};
表示颜色的,应该是8位表示红色,8位表示绿色,8位表示蓝色,图像处理中的二进制量化。
等后面需要深入opengl的时候再来了解GLContextAttrs和GLView的作用,现在只知道需要这么做,初始化图形底层接口。
#include "AppDelegate.h"
#include "cocos2d.h"
#include "controller.h"
#include "editor-support/cocostudio/CocoStudio.h"
#include "extensions/cocos-ext.h"
USING_NS_CC;
AppDelegate::AppDelegate()
: _testController(nullptr)
{
}
AppDelegate::~AppDelegate()
{
//SimpleAudioEngine::end();
cocostudio::ArmatureDataManager::destroyInstance();
}
// if you want a different context, modify the value of glContextAttrs
// it will affect all platforms
void AppDelegate::initGLContextAttrs()
{
// set OpenGL context attributes: red,green,blue,alpha,depth,stencil
GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8};
GLView::setGLContextAttrs(glContextAttrs);
}
bool AppDelegate::applicationDidFinishLaunching()
{
// As an example, load config file
// FIXME:: This should be loaded before the Director is initialized,
// FIXME:: but at this point, the director is already initialized
Configuration::getInstance()->loadConfigFile("configs/config-example.plist");
// initialize director
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if(!glview) {
glview = GLViewImpl::create("Cpp Tests");
director->setOpenGLView(glview);
}
director->setDisplayStats(true);
director->setAnimationInterval(1.0f / 60);
auto screenSize = glview->getFrameSize();
auto designSize = Size(480, 320);
auto fileUtils = FileUtils::getInstance();
std::vector<std::string> searchPaths;
if (screenSize.height > 320)
{
auto resourceSize = Size(960, 640);
searchPaths.push_back("hd");
searchPaths.push_back("ccs-res/hd");
searchPaths.push_back("ccs-res");
searchPaths.push_back("Manifests");
director->setContentScaleFactor(resourceSize.height/designSize.height);
searchPaths.push_back("hd/ActionTimeline");
}
else
{
searchPaths.push_back("ccs-res");
searchPaths.push_back("ActionTimeline");
}
fileUtils->setSearchPaths(searchPaths);
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8)
// a bug in DirectX 11 level9-x on the device prevents ResolutionPolicy::NO_BORDER from working correctly
glview->setDesignResolutionSize(designSize.width, designSize.height, ResolutionPolicy::SHOW_ALL);
#else
glview->setDesignResolutionSize(designSize.width, designSize.height, ResolutionPolicy::NO_BORDER);
#endif
// Enable Remote Console
auto console = director->getConsole();
console->listenOnTCP(5678);
_testController = TestController::getInstance();
// To enable built-in VR, use this line.
// auto vrImpl = new VRGenericRenderer;
// glview->setVR(vrImpl);
return true;
}
// This function will be called when the app is inactive. Note, when receiving a phone call it is invoked.
void AppDelegate::applicationDidEnterBackground()
{
if (_testController)
{
_testController->onEnterBackground();
}
Director::getInstance()->stopAnimation();
}
// this function will be called when the app is active again
void AppDelegate::applicationWillEnterForeground()
{
if (_testController)
{
_testController->onEnterForeground();
}
Director::getInstance()->startAnimation();
}
applicationDidEnterBackground, applicationWillEnterForground没什么讲的,
AppDelegate类和TestController类应该是属于聚合关系,网上查了一下类的关系,组合和聚合比较难以分辨,基本上两者差不错。或者可以打这样一个比方,组合比聚合更紧密,人有一个脑袋,人和脑袋就是组合关系,人和世界是属于聚合关系,没有人,世界依然会存在,不知道这样比喻是否贴切。
网上找的一个图,表示它们两者的关系。
applicationDidFinishLaunching做的事情比较多,
.plist是读文件,这个文件的后缀不常见,看到list也就可以联想到是一系列文件,p大概是push的意思,大概是把一系列的文件放大一个文件中,节约内存。
后面会专门介绍png图像,以及其它的文件的制作,这里不展开叙述。
游戏中的字体部分也十分的关键。
Director是一个单例模式,
单例表示整个程序中只有这么一个,就像Director描述的那样整个电影就只有一个导演。
里面有非常多程序的属性以及方法之类的类,截取部分
//texture cache belongs to this director
TextureCache *_textureCache;
float _animationInterval;
float _oldAnimationInterval;
/* landscape mode ? */
bool _landscape;
bool _displayStats;
float _accumDt;
float _frameRate;
LabelAtlas *_FPSLabel;
LabelAtlas *_drawnBatchesLabel;
LabelAtlas *_drawnVerticesLabel;
可以看到还能设置是否横竖屏,
Director类没有构造器,只能通过getInstance()这个方法获取
Director* Director::getInstance()
{
if (!s_SharedDirector)
{
s_SharedDirector = new (std::nothrow) Director;
CCASSERT(s_SharedDirector, "FATAL: Not enough memory");
s_SharedDirector->init();
}
return s_SharedDirector;
}
同时可以看到s_ShareDirector是一个静态成员变量,整个程序只能存在一个,很常用的单例模式构造模型。
static Director *s_SharedDirector = nullptr;
同时可以看到程序根据屏幕尺寸的大小做了一些适配,读取不同的文件夹。
屏幕适配是个比较复杂的问题,如果使用cocos new新建的工程,和这个有点不同,
屏幕适配还是个比较烦人的问题。
最后这句话
_testController = TestController::getInstance();
走进另外一个类,TestController,也是一个单例模式。
到这里AppDelegate.cpp这个类基本上讲完了,留下一个大概的印象就可以了,关键是后面怎么调用api,
其实也可以看一个空工程的例子。
有时直接
Director->getInstance()->replaceScene(NewScene::createScene());
调到一个新的场景中,很少有什么东西在这个类中实现。
接下来看为了构建整个工程,其它和功能无关的辅助类,比如这个TestController,顾名思义,就是用来控制所有需要测试的case的。