Cocos2d-x 3.3塔防游戏《保卫萝卜》教程03:Hello Game项目解析
在第一课中,我们详细讲解了开发环境的搭建;第二课则聚焦于项目的创建。完成这两课学习的同学,现在可以开启新课——项目解析的学习之旅了。
一、前提条件
完成Hello Game项目的创建与编译。具体可参考:Cocos2d-x 3.3塔防游戏《保卫萝卜》教程02:Hello Game项目创建
二、本篇目标
- 深入分析proj.win32工程的主要构成。
- 全面剖析proj.android工程的主要构成。
- 新建一个MyScene.cpp文件,并在游戏中成功显示该场景。
- 在Android真机上运行游戏,查看实际效果。
三、详细分析
(一)游戏开发流程概述
在游戏开发过程中,通常遵循以下步骤:首先,在Microsoft Visual Studio 2012中针对proj.win32工程编写代码,并在Windows系统上进行调试和运行。当游戏的主体开发工作完成后,进行so文件的编译和打包。随后,在Eclipse的proj.android工程中编写少量代码,以完成游戏在Android平台下的打包开发。
(二)proj.win32工程主要构成分析
- 工程打开方式:使用Microsoft Visual Studio 2012打开proj.win32工程。
- 工程组成部分
- 整个hellogame解决方案由hellogame、libbox2d、libcocos2d、libSpine四个工程项目构成。
- Hellogame工程:作为游戏的主工程,开发工作主要在此工程中开展。
- libbox2d工程:这是一个用于模拟2D刚体物体的C++物理引擎,像《植物大战僵尸》《愤怒的小鸟》等知名游戏都得益于该引擎。
- libcocos2d工程:它是整个Cocos2d - x的核心部分。
- libSpine工程:属于工具软件支持库。
- 后续将重点对Hellogame工程的代码进行解析,libbox2d工程会在后面的物理引擎篇中详细讲解,而另外两个工程将在后续使用到的篇幅中进行介绍。
- Hellogame工程源代码分析
- 文件组成:工程主要由src目录下的AppDelegate.cpp、AppDelegate.h、HelloWorldScene.cpp、HelloWorldScene.h四个源文件,以及win32目录下的main.cpp、main.h两个源文件组成。
- 代码特性:src目录下的源文件是所有6个平台共用的代码文件,无论是Android还是iOS平台,都使用该目录下的源文件,属于真正的跨平台代码部分。而Win32目录下的源文件只是一个main主入口文件,负责在win32平台下对游戏进行调用。在对应的proj.android工程里,也有一个Android平台对应的main主入口文件,虽然由于平台不同,实现代码有所差异,但目的是一致的。
4. AppDelegate.cpp源代码解析
AppDelegate类似于Android的Application,它提供了一些应用程序级别的状态回调,整个游戏应用程序由该文件中的方法进行控制。以下是几个主要方法的详细说明:
#include "AppDelegate.h"
#include "HelloWorldScene.h"
// 命名空间宏,引入cocos2d的头文件
USING_NS_CC;
AppDelegate::AppDelegate() {
}
AppDelegate::~AppDelegate() {
}
// 设置 OpenGL context,此设置对所有平台都有效
void AppDelegate::initGLContextAttrs() {
// 设置 OpenGL context 属性,目前只能设置6个属性:red, green, blue, alpha, depth, stencil
GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8};
GLView::setGLContextAttrs(glContextAttrs);
}
// 当应用程序启动时执行,是游戏程序的启动入口
// 通常在这里启动loading界面,游戏从这里开始
bool AppDelegate::applicationDidFinishLaunching() {
// 初始化 director
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if (!glview) {
glview = GLViewImpl::create("My Game");
director->setOpenGLView(glview);
}
// 在屏幕上显示FPS数,开发阶段建议开启,发布时关闭
director->setDisplayStats(true);
// 设置 FPS数,默认值为 1.0 / 60
director->setAnimationInterval(1.0 / 60);
// 创建一个HelloWorld的scene,该对象会自动回收
auto scene = HelloWorld::createScene();
// 告诉director运行HelloWorld的scene
director->runWithScene(scene);
return true;
}
// 当游戏进入后台时调用,如按下Android手机的home键或有电话打入
void AppDelegate::applicationDidEnterBackground() {
Director::getInstance()->stopAnimation();
// 如果游戏使用了SimpleAudioEngine,必须在此暂停
// SimpleAudioEngine::getInstance()->pauseBackgroundMusic();
}
// 当游戏恢复到前台运行时调用,如接电话结束后
void AppDelegate::applicationWillEnterForeground() {
Director::getInstance()->startAnimation();
// 如果游戏使用了SimpleAudioEngine,必须在此恢复
// SimpleAudioEngine::getInstance()->resumeBackgroundMusic();
}
上述代码中涉及到的director(导演)负责游戏场景的显示和切换,如同电影导演掌控整个电影的流程;scene(场景)负责显示一个游戏场景,类似于电影中的一个场景镜头。其中,applicationDidFinishLaunching()方法是最重要的,因为游戏从该方法开始执行。
5. HelloWorldScene.cpp源代码解析
在AppDelegate类的applicationDidFinishLaunching()方法中,创建并运行了一个HelloWorldScene的场景,HelloWorldScene.cpp就是该场景的具体代码实现。以下是几个主要方法的说明:
#include "HelloWorldScene.h"
USING_NS_CC;
// 创建场景
Scene* HelloWorld::createScene() {
// 创建一个自释放的场景对象
auto scene = Scene::create();
// 创建一个自释放的画面层对象
auto layer = HelloWorld::create();
// 将创建的画面层添加到场景中,一个场景可添加多个画面层
scene->addChild(layer);
// 返回创建的场景
return scene;
}
// 场景初始化方法
bool HelloWorld::init() {
// 首先进行父类初始化
if (!Layer::init()) {
// 如果初始化父类失败,返回false
return false;
}
// 获取整个手机可视屏幕尺寸
Size visibleSize = Director::getInstance()->getVisibleSize();
// 获取手机可视屏原点的坐标
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// 创建一个带图标的关闭按钮,点击后调用menuCloseCallback方法退出游戏
auto closeItem = MenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
// 设置关闭按钮的显示位置,显示在可视屏幕的右下角
closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width / 2,
origin.y + closeItem->getContentSize().height / 2));
// 创建一个可自释放的菜单
auto menu = Menu::create(closeItem, NULL);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
// 创建一个显示"Hello Game"文字的Label
auto label = Label::createWithTTF("Hello Game", "fonts/Marker Felt.ttf", 24);
// 设置label在屏幕中的显示位置
label->setPosition(Vec2(origin.x + visibleSize.width / 2,
origin.y + visibleSize.height - label->getContentSize().height));
// 将label添加到画面层
this->addChild(label, 1);
// 创建一个带图片的精灵
auto sprite = Sprite::create("HelloWorld.png");
// 设置图片精灵的显示位置
sprite->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y));
// 添加图片精灵到画面层
this->addChild(sprite, 0);
return true;
}
// 退出按钮事件
void HelloWorld::menuCloseCallback(Ref* pSender) {
// 当是wp8或者winrt平台时
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.", "Alert");
return;
#endif
// 结束Director
Director::getInstance()->end();
// 当是ios平台时退出
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
exit(0);
#endif
}
上述代码是一个简单的Scene(场景)实现代码,在实际游戏开发中,就是通过制作一个又一个的场景,并利用Director进行调度和组织,从而构成一个完整的游戏。
(三)proj.android工程主要构成分析
- 工程打开方式:使用Eclipse打开proj.android工程。
- 工程组成部分
- 整个hellogame的proj.android工程相比win32的要简单。除了Android项目必须的一些组成部分外,还包括以下几个重要部分:
- Classes文件夹:该文件夹中的源文件与proj.win32中的源文件是共享的。
- jni/hellocpp/main.cpp:此文件相当于proj.win32中win32目录下的源文件,是主入口文件。
- libs/armeabi/libcocos2dcpp.so:这是整个游戏代码的编译包,真正的游戏代码实现最终由C++文件编译打包成这个so类库,供Android代码调用以运行游戏。
- 整个游戏的开发基本不需要在Eclipse中编写代码,Android平台只需调用编译好的so文件即可运行游戏。在Eclipse中,只需将包含了so文件的Android工程打包成apk文件进行发布。
(四)新建一个MyScene.cpp并在游戏中显示
- 使用Microsoft Visual Studio 2012打开proj.win32工程:我们将在这个工程中新增一个自己的Scene(场景)并显示出来。虽然这个任务看似简单,但对于新手来说可能会遇到一些困难,因此下面进行详细演示。
- 文件新建方法
- 第一种方式
- 第一步:在右边的解决方案资源管理器中,右键点击src,选择新建类。
- 问题:此时会发现“浏览”按钮不可用,新建的cpp文件只能创建到目录hellogame\proj.win32下,这会引发一些问题。
- 第二步:先忽略这个问题,按照提示继续点击“添加”,进入类向导界面,输入类名MyScene,然后点击完成类的创建。此时,会在proj.win32目录下新增MyScene.cpp和MyScene.h两个文件。
- 第二种方式
- 第一步:在右边的解决方案资源管理器中,右键点击src,选择新建项。
- 问题:此时“浏览”按钮可用,点击修改目录为hellogame\Classes下,并且需要选择是新建.cpp文件还是.h文件。
- 第二步:先选择.cpp类型,输入MyScene.cpp,完成创建。然后继续前面的步骤,新建MyScene.h文件。此时,在Classes目录下会出现新增的MyScene.cpp和MyScene.h两个文件。
- 代码编写
- MyScene.h
#include "cocos2d.h"
- MyScene.h
class MyScene : public cocos2d::Layer { public: static cocos2d::Scene* createScene(); virtual bool init(); CREATE_FUNC(MyScene); };
- **MyScene.cpp**
include "MyScene.h"
USING_NS_CC;
Scene* MyScene::createScene() { auto scene = Scene::create(); auto layer = MyScene::create(); scene->addChild(layer); return scene; }
bool MyScene::init() { if (!Layer::init()) { return false; } // 获取整个手机可视屏幕尺寸 Size visibleSize = Director::getInstance()->getVisibleSize(); // 获取手机可视屏原点的坐标 Vec2 origin = Director::getInstance()->getVisibleOrigin(); // 创建一个显示"MyScene"文字的Label auto label = Label::createWithTTF("MyScene", "fonts/Marker Felt.ttf", 24); // 设置白色 label->setColor(Color3B::WHITE); // 设置label在屏幕中的显示位置 label->setPosition(Vec2(origin.x + visibleSize.width / 2, origin.y + visibleSize.height - label->getContentSize().height)); // 将label添加到画面层 this->addChild(label, 1); return true; }
4. **在游戏中显示MyScene**
完成MyScene的编写后,我们需要在游戏开启后的界面中添加一个按钮菜单,点击该按钮后进入MyScene场景。游戏开启后的界面场景是HelloWorldScene,因此需要在HelloWorldScene中添加一个按钮,并为该按钮添加一个点击启动MyScene的事件。
- **HelloWorldScene.h**:在该文件中首先声明一个按钮点击事件。
// 切换到下一个scene事件 void menuNextCallback(cocos2d::Ref* pSender);
- **HelloWorldScene.cpp**
include "HelloWorldScene.h"
include "MyScene.h"
USING_NS_CC;
// 实现menuNextCallback事件代码 // 按钮点击事件,点击后启动MyScene void HelloWorld::menuNextCallback(Ref* pSender) { // 新建MyScene实例 auto scene = MyScene::createScene(); // 用MyScene实例替换当前scene Director::getInstance()->replaceScene(scene); }
// 添加在屏幕上添加启动按钮 // 设置关闭按钮的显示位置,显示在可视屏幕的右下角 closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width / 2, origin.y + closeItem->getContentSize().height / 2)); // 新建一个带图片的按钮菜单 auto goItem = MenuItemImage::create("next_1.png", "next_2.png", CC_CALLBACK_1(HelloWorld::menuNextCallback, this)); goItem->setPosition(Vec2(origin.x + visibleSize.width / 2 - closeItem->getContentSize().width / 2, origin.y / 2 + closeItem->getContentSize().height / 2)); // 创建一个可自释放的菜单 auto menu = Menu::create(closeItem, goItem, NULL); menu->setPosition(Vec2::ZERO); this->addChild(menu, 1);
5. **编译问题及解决方法**
- **问题**:如果使用第一种方法创建的MyScene,可能会出现无法通过编译的情况,报错信息如下:
IntelliSense: 无法打开 源 文件 "MyScene.h" error C1083: 无法打开包括文件:“MyScene.h”: No such file or directory
- **解决方法**
- 第一步:在项目名上点击右键,选择属性。
- 第二步:选择左边的C/C++,然后在右边的附加包含目录中追加:;$(ProjectDir)。每个人的附加包含目录可能会因环境不同而有所区别,示例如下:
$(EngineRoot)cocos\audio\include;$(EngineRoot)external;$(EngineRoot)external\chipmunk\include\chipmunk;$(EngineRoot)extensions;..\Classes;..;%(AdditionalIncludeDirectories);$(ProjectDir)
完成上述设置后,进行项目调试运行,即可正常运行,并且点击按钮后能成功显示MyScene的画面,达到我们设定的目标。
### (五)在Android真机上运行查看效果
1. **so文件的打包编译**
- 第一步:如果上面的步骤中是按照第一种方法创建的MyScene,需要将MyScene.cpp、MyScene.h两个文件从proj.win32文件拷贝到Classes文件夹下。如果是按照第二种方法创建的MyScene,则可忽略本步骤。
- 第二步:使用EditPlus等文本编辑器打开proj.android\\jni目录下的Android.mk文件,将MyScene.cpp添加到需要编译的源文件清单中,然后保存,示例如下:
LOCAL_SRC_FILES := hellocpp/main.cpp \ ../../Classes/AppDelegate.cpp \ ../../Classes/HelloWorldScene.cpp \ ../../Classes/MyScene.cpp
- 第三步:开启Cygwin开始编译打包so文件。如果不会操作,请参考:[Cocos2dx.3x_Hello Game项目创建篇](此处可补充具体链接)
- 第四步:打包so文件成功后,开启Eclipse打开游戏工程,并连接Android手机,运行工程,查看真机运行的效果是否与前面Windows中的效果一致。