本文關於HelloWorld的分析希望有利於你的學習,本文難免有所錯誤,如有問題歡迎留言
目錄:
1、main
2、AppDelegate
3、HelloWorld
main
#include "main.h"
#include "AppDelegate.h"
#include "cocos2d.h"
USING_NS_CC;
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// create the application instance
AppDelegate app;
return Application::getInstance()->run();
}
AppDelegate app;
創建一個AppDelegate對象,通過getInstance()獲取當前應用的實例(該實例爲一個靜態成員變量,在AppDelegate的構造函數中進行了初始化)。
run()
創建、運行窗口,不停的繪製界面,以及進行消息循環。
問題一:爲什麼我們聲明的是一個AppDelegate的一個對象app,但是我們調用卻是使用Application::getInstance()->run();來調用AppDelegate類中的run?
首先,AppDelegate是繼承於Application,在聲明 AppDelegate對象app時,會先初始化父類的構造函數然後是子類的構造函數,此時我們的Application的靜態成員就已經初始化完畢(類的靜態成員本質爲一個全局變量),通過該實例我們就能調用run()函數。
Application的構造函數
Application * Application::sm_pSharedApplication = 0;
Application::Application()// 該構造函數在AppDelegate app;聲明的時候構造的
: _instance(nullptr)
, _accelTable(nullptr)
{
_instance = GetModuleHandle(nullptr);
_animationInterval.QuadPart = 0;
CC_ASSERT(! sm_pSharedApplication);
sm_pSharedApplication = this;//此處給我們的靜態成員賦值
}
Application* Application::getInstance()//此方法爲一個靜態方法
{
CC_ASSERT(sm_pSharedApplication);
return sm_pSharedApplication;
}
sm_pSharedApplication = this;該句是實現的關鍵
問題二:該句實現的原理是什麼呢?
我們先來了解下類繼承的內存結構
首先我們需要知道,同一個類,無論產生多少個實例,它們都是公用同一個虛表。
回到我們的程序上,通過內存我們不難發現,this就是AppDelegate的首地址,和虛表指針的首地址相同。sm_pSharedApplication = this後,sm_pSharedApplication獲取的就是虛表指針的值,指向虛表的首地址。而sm_pSharedApplication爲一個Application的類實例,我們可以理解爲它從AppDelegate虛表中截取了Application部分的虛表,而這些虛表我們在AppDelegate中繼承實現,調用相關虛函數時其實就是調用AppDelegate實現的虛函數。如果還無法理解,再去了解在接口的原理。
run()函數講解
int Application::run()
{
//PVRVFrame是仿真庫,允許OpenGL ES的應用程序可以在本身不支持OpenGL ES的API的桌面開發機器上運行的集合。
PVRFrameEnableControlWindow(false);
// Main message loop:主消息循環
LARGE_INTEGER nLast;// 聲明一個LARGE_INTEGER聯合體(64位)變量nLast
LARGE_INTEGER nNow;
// 此函數用於獲取精確的性能計數器數值,將計數器數值的地址存放到nLast中
// 該計數器數值,就是CPU運行到現在的時間(微秒級)
QueryPerformanceCounter(&nLast);
// 初始化 OpenGL 上下文(就是初始化OpenGL)
initGLContextAttrs();
// 初始化了場景、適配器尺寸和其他相關的參數。注意了,applicationDidFinishLaunching爲繼承父類的純虛函數,具體的實現是在子類AppDelegate中。
// 如果你不夠了解多態與虛函數機制,可能還是無法理解爲什麼在子類中實現,在這裏使用。這其實並不難,你也可以看下我關於多態與虛表的博文
if (!applicationDidFinishLaunching())
{
return 1;
}
auto director = Director::getInstance();// 通過單例模式獲取Director導演實例
auto glview = director->getOpenGLView();// 獲取繪製所有對象的OpenGL視圖
// Retain的意思是保持引用,如果我們想保持某個對象的引用,避免它被Cocos2d-x釋放,就需要調用retain
// 此時內存管理機制就不會進行自動釋放glview,而需要手動調用release釋放該資源,否則會造成內存泄漏
glview->retain();
// 如果窗口未關閉,則一直進行消息循環
while(!glview->windowShouldClose())
{
// 獲取CPU運行到現在的時間,將該時間保存到nNow變量中
QueryPerformanceCounter(&nNow);
// 計算當前時間與上一幀的時間間隔是否大於設定每幀的時間間隔(默認60幀/秒)
// _animationInterval 幀率的設置在AppDelegate的applicationDidFinishLaunching中
if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)
{
// 重置上一幀的時間,nNow.QuadPart % _animationInterval.QuadPart爲了方便取整
// 有的寫法寫成 nLast.QuadPart= nNow.QuadPart; 也是可以的
nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart);
// 進行主消息循環,進行對消息進行處理(如果你瞭解win32消息機制,理解這個完全沒有問題)
director->mainLoop();
// 處理一些事件
glview->pollEvents();
}
else
{
Sleep(1);
}
}
// 關閉後,清理導演資源
if (glview->isOpenGLReady())
{
director->end();
director->mainLoop();
director = nullptr;
}
glview->release();// 手動釋放glview資源,因爲我們前面glview->retain();後需要自己進行對該資源的內存管理
return 0;
}
applicationDidFinishLaunching()調用的原理,我們在上面已經講解。
小結下:run中主要進行了相關資源的初始化,之後進入消息循環,消息循環中處理我們的消息,直到我們發出退出事件消息,然後清理資源,退出程序。
AppDelegate
AppDelegate.h
#include "cocos2d.h"
class AppDelegate : private cocos2d::Application
{
public:
AppDelegate();
virtual ~AppDelegate();
// 繼承於父類Application的虛函數,而Application繼承於ApplicationProtocol
// 在這裏實現後,Application就能調用該實現
virtual void initGLContextAttrs();
virtual bool applicationDidFinishLaunching();
virtual void applicationDidEnterBackground();
virtual void applicationWillEnterForeground();
};
方法:applicationDidFinishLaunching
bool AppDelegate::applicationDidFinishLaunching() {
// 初始化導演類(單例模式,後面再講解)
auto director = Director::getInstance();
// 進行一個窗口大小(分辨率)適配的相關處理
auto glview = director->getOpenGLView();
if(!glview) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
glview = GLViewImpl::createWithRect("HelloTestCpp", Rect(0, 0, designResolutionSize.width, designResolutionSize.height));
#else
glview = GLViewImpl::create("HelloTestCpp");
#endif
director->setOpenGLView(glview);
}
// 顯示我們的FPS(幀的頻率)
director->setDisplayStats(true);
// 設置FPS值,默認值爲1.0/60(每秒鐘繪製60次)
director->setAnimationInterval(1.0 / 60);
// 分辨率的相關設置
glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
Size frameSize = glview->getFrameSize();
if (frameSize.height > mediumResolutionSize.height)
{
director->setContentScaleFactor(MIN(largeResolutionSize.height/designResolutionSize.height, largeResolutionSize.width/designResolutionSize.width));
}
else if (frameSize.height > smallResolutionSize.height)
{
director->setContentScaleFactor(MIN(mediumResolutionSize.height/designResolutionSize.height, mediumResolutionSize.width/designResolutionSize.width));
}
else
{
director->setContentScaleFactor(MIN(smallResolutionSize.height/designResolutionSize.height, smallResolutionSize.width/designResolutionSize.width));
}
// 加載我們額外的包
register_all_packages();
// 創建一個HelloWorld的場景
auto scene = HelloWorld::createScene();
// 通過導演類顯示場景
director->runWithScene(scene);
return true;
}
前面我們瞭解到,applicationDidFinishLaunching的調用是在Application中。也是我們直觀的程序啓動函數。
通過導演類加載並顯示我們遊戲的第一個場景,開始遊戲。
方法:applicationDidEnterBackground
void AppDelegate::applicationDidEnterBackground() {
Director::getInstance()->stopAnimation();
}
這裏寫入的是我們遊戲進入後臺時進行的操作
stopAnimation:停止動畫。不進行繪製。主循環不會再被觸發。 如果你不想暫停動畫,請調用[pause]。
方法:applicationWillEnterForeground
void AppDelegate::applicationWillEnterForeground() {
Director::getInstance()->startAnimation();
}
這裏寫入的是我們遊戲由後臺切入到前景時進行的操作
startAnimation:主循環觸發一次。 只有之前調用過stopAnimation,才能調用這個函數。
HelloWorld
HelloWorld.h
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
class HelloWorld : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
// 按鈕的回調函數
void menuCloseCallback(cocos2d::Ref* pSender);
// 實現“靜態create()”方法
CREATE_FUNC(HelloWorld);
};
#endif // __HELLOWORLD_SCENE_H__
方法:createScene
Scene* HelloWorld::createScene()
{
auto scene = Scene::create();// 創建一個場景
auto layer = HelloWorld::create();// 創建 HelloWord 的圖層
scene->addChild(layer);// 將 HelloWord 圖層加入到場景中
return scene; // 返回我們創建的場景
}
接下來我們來逐句解釋下相關的實現
首先我們來看一下Scene::create()的實現
Scene* Scene::create()
{
Scene *ret = new (std::nothrow) Scene();
if (ret && ret->init())// init 進行初始化場景大小操作
{
ret->autorelease();// 將場景對象加入到內存回收池中(cocos引用計數內存管理)
return ret;
}
else
{
CC_SAFE_DELETE(ret);// 刪除對象的宏,就是執行的 delete ret;
return nullptr;
}
}
不難看出,這裏進行了場景的創建,並且將創建的場景對象放入cocos自動管理內存釋放的內存回收池中進行管理,隨後返回我們創建的場景。
關於new (std::nothrow)與標準new的區別
new在分配內存失敗時會拋出異常,而”new(std::nothrow)”在分配內存失敗時會返回一個空指針(NULL)。具體的std::nothrow實現原理可以查閱瞭解
我們再來了解下HelloWorld::create()的實現
靜態的create方法在頭文件通過宏 CREATE_FUNC(HelloWorld); 聲明,以下爲宏的原型
#define CREATE_FUNC(__TYPE__)
static __TYPE__* create()
{
__TYPE__ *pRet = new(std::nothrow) __TYPE__();
if (pRet && pRet->init())
{
pRet->autorelease();
return pRet;
}
else
{
delete pRet;
pRet = nullptr;
return nullptr;
}
}
__TYPE__就是我們傳入的HelloWorld類,我們這裏如果把所有的__TYPE__替換成HelloWorld,聰明的你應該一下就理解了,而且不難發現它和場景的Scene::create()方法基本沒有什麼不同,都是創建一個類的實例,並調用自身的init()進行相應的初始化。然後對該對象進行內存管理。
最後是scene->addChild(layer);的實現,其實就是將我們創建的HelloWorld圖層添加到我們的場景中(爲什麼HelloWorld類是一個圖層?因爲HelloWorld是繼承於Layer的),因爲導演播放的是場景,而場景裏面是圖層,圖層裏面再是一些精靈以及其他節點等… 這整個就是個樹形結構,其實引擎的渲染也是樹形渲染(大家也可以去了解下引擎渲染的原理),這裏再回顧下各個節點之間的關係圖
方法:init
bool HelloWorld::init()
{
//////////////////////////////
// 1. super init first
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
/////////////////////////////
// 2. add a menu item with "X" image, which is clicked to quit the program
// you may modify it.
// add a "close" icon to exit the progress. it's an autorelease object
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));
// create menu, it's an autorelease object
auto menu = Menu::create(closeItem, NULL);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
/////////////////////////////
// 3. add your codes below...
// add a label shows "Hello World"
// create and initialize a label
auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
// position the label on the center of the screen
label->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - label->getContentSize().height));
// add the label as a child to this layer
this->addChild(label, 1);
// add "HelloWorld" splash screen"
auto sprite = Sprite::create("HelloWorld.png");
// position the sprite on the center of the screen
sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
// add the sprite as a child to this layer
this->addChild(sprite, 0);
return true;
}
總的來說,就是創建了一些精靈,然後添加到圖層上面…
Layer::init()調用父類Layer的init初始化函數,對屏幕的適配大小初始化
bool Layer::init()
{
Director * director = Director::getInstance();
setContentSize(director->getWinSize());
return true;
}
getVisibleSize:獲得可視區域的大小,若是DesignResolutionSize跟屏幕尺寸一樣大,則getVisibleSize便是getWinSize。(以像素爲單位)
getVisibleOrigin:表示可視區域的起點座標,這在處理相對位置的時候非常有用,確保節點在不同分辨率下的位置一致。
方法:menuCloseCallback
void HelloWorld::menuCloseCallback(Ref* pSender)
{
Director::getInstance()->end();
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
exit(0);
#endif
}
圖片按鈕添加的事件處理函數(和win32裏面的窗口回調,以及java裏面的事件監聽都差不多)。具體是事件機制可以查閱資料瞭解
呼~終於是寫完了,希望大家能夠有所收穫