存档

2012年8月 的存档

How to mask sprites in cocos2d by the alpha of a gradient?

2012年8月27日 没有评论

I’d like to make it so that a CCNode (could be CCLayer, CCSprite, etc) can mask all of its children with a shader so that they can be masked progressively as they approach the bounds of the node without having to modify their opacity value directly. For example, a menu might look like this:

How to mask sprites in cocos2d by the alpha of a gradient?

The mask in this case would be a gradient, that might look something like this:

How to mask sprites in cocos2d by the alpha of a gradient?

The basic tutorial for creating a masking sprite is available at Ray Wenderlich’s website, though I’m not sure if it will allow you to mask children nodes as well. If not, then you’ll probably have to use CCRenderTexture, draw children nodes to texture, then finally mask it with the masking node created in tutorial.

I know that this is probably not the mechanism you want, but I recently wanted a gradient alpha that varied in a circular manner. I had some text that would rotate around a sprite and I only wanted it to be visible when it was at the top of the circle.

The simplest approach for me was to create a sprite that was a circular band where the band was a full “black” at the bottom and graduated back to fully transparent at the top.

By placing this above in the z-order, the text I then got the effect I was after. I imagine you could achieve the same sort of effect this way

To see it in action, take a look at the main menu of my app, Claustrophobic.

分类: stackoverflow精选, unity3d 标签:

Cocos2d-x 的“HelloLua” 深入分析

2012年8月25日 没有评论

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier]  

红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x

本章为我的Cocos2d-x教程一书初稿。望各位看官多提建议!

转载请注明来源:红孩儿的游戏编程之路 CSDN博客


Cocos2d-x 的“HelloLua” 深入分析


注:本章需要一定Lua经验和内存管理编程相关经验

另:Cocos2d-x版本为http://cn.cocos2d-x.org/download:
cocos2d-1.0.1-x-0.12.0 @ Mar 05, 2012


     我们来看一下Cocos2d-x的HelloLua示例工程。首先编译运行一下这个工程,当然,因为我在HelloWorld工程中改动了CCApplication的run函数和initInstance函数,所以这里要修改一下,与HelloWorld保持一致才能编译成功。哇!一个很COOL的农场游戏。

                     Cocos2d-x 的“HelloLua” 深入分析

       这几乎是我见过的最令人激动的示例了。农场类游戏两年前可是非常火的!怎么做的,马上来看。

       main.h和main.cpp与HelloWorld无异。不理会了。打开AppDelegate.cpp,可以看到它与HelloWorld工程中的AppDelegate.cpp的明显不同是使用了声音引擎类SimpleAudioEngine和脚本引擎类CCScriptEngineManager。下面我们来分析一下源码。

应用程序启动时调用的函数
bool AppDelegate::applicationDidFinishLaunching()
{
	// 初始化显示设备
	CCDirector *pDirector = CCDirector::sharedDirector();
	// 设置显示设备使用initInstance函数创建的OpenGL视窗
    pDirector->setOpenGLView(&CCEGLView::sharedOpenGLView());
	//使用高清模式
    // pDirector->enableRetinaDisplay(true);

	// 设置显示FPS
	pDirector->setDisplayFPS(true);
	//设置设备的显示方向
	// pDirector->setDeviceOrientation(kCCDeviceOrientationLandscapeLeft);
	//设置FPS帧间隔时间
	pDirector->setAnimationInterval(1.0 / 60);

	// 通过CCLuaEngine的静态函数获取一个LUA脚本引擎实例对象指针
CCScriptEngineProtocol* pEngine = CCLuaEngine::engine();
// 通过CCScripEngineManager的静态函数sharedManager获取单件脚本引擎管理器的实例对象指针,并将上一句创建的LUA脚本引擎实例对象指针设为脚本引擎管理器当前进行管理的脚本引擎。
    CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
	//在ANDROID平台下,会通过文件处理API类CCFileUtils中的getFileData取得hello.lua文件并读取到char数组中。
	unsigned long size;
	char *pFileContent = (char*)CCFileUtils::getFileData("hello.lua", "r", &size);

	if (pFileContent)
	{
	    //将char数组数据放入到一个新的以’/0’结尾的char数组中形成一个LUA脚本字符串
	    char *pCodes = new char[size + 1];
	    pCodes[size] = '/0';
	    memcpy(pCodes, pFileContent, size);
	    delete[] pFileContent;
		//让脚本引擎执行这个LUA脚本字符串
	    pEngine->executeString(pCodes);
		//释放动态申请的char数组的内存
	    delete []pCodes;
	}
#endif

#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) || (CC_TARGET_PLATFORM == CC_PLATFORM_MARMALADE)
	//如果是Win32,IOS或MARMALADE平台,通过文件处理API类CCFileUtils中的fullPathFromRelativePath函数产生一个hello.lua在当前程序所在目录下的路径。具体实现可参看CCFileUtils_win32.cpp
	string path = CCFileUtils::fullPathFromRelativePath("hello.lua");
	//将这个路径的目录放入到脚本引擎的搜索目录
	//path.substr会将路径中的目录取出来。
pEngine->addSearchPath(path.substr(0, path.find_last_of("/")).c_str());
//执行这个路径所指向的LUA文件
    pEngine->executeScriptFile(path.c_str());
#endif 

	return true;
}


       我们没有在这里面发现任何关于农场或松鼠的只言片语。我们只知道程序执行了一下“hello.lua”文件。多COOL!

       我已经迫不急待的想要打开hello.lua文件一看究竟了。我们打开工程下的Resource目录,可以发现农场和松鼠的图片,还有一些声音文件,以及我们要找的lua文件,共有两个:hello.lua和hello2.lua。

Cocos2d-x 的“HelloLua” 深入分析

点击打开hello.lua,我们来分析一下:

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier] 
-- 设定LUAGC的拉圾回收参数
collectgarbage("setpause", 100)
collectgarbage("setstepmul", 5000)
--这里定义一个函数cclog,用来打印字符串参数
local cclog = function(...)
    print(string.format(...))
end
--将hello2.lua包含进来,hello2.lua定义了myadd函数的实现
require "hello2"
--这里调用cclog打印一个和的结果,并没什么实际用处,可略过
cclog("result is " .. myadd(3, 5))

---------------
--通过CCDirector:sharedDirector()来取得显示设备实例对象,并调用其getWinSize函数取得窗口大小给变量winSize
local winSize = CCDirector:sharedDirector():getWinSize()

-- 定义createDog函数创建松鼠
local function creatDog()
    --定义两个变量为每一帧图块的宽高
    local frameWidth = 105
    local frameHeight = 95

-- 创建松鼠的动画
-- 先使用CCTextureCache:sharedTextureCache()取得纹理块管理器,将dog.png放入纹理块管理器产生一张纹理返回给变量textureDog
local textureDog = CCTextureCache:sharedTextureCache():addImage("dog.png")
--创建一个矩形返回给变量rect 
local rect = CCRectMake(0, 0, frameWidth, frameHeight)
--由这个矩形从纹理上取出图块产生一个CCSpriteFrame指针返回给变量frame0
local frame0 = CCSpriteFrame:frameWithTexture(textureDog, rect)
--换一个新的位置的矩形返回给变量rect中
rect = CCRectMake(frameWidth, 0, frameWidth, frameHeight)
--由第二个矩形从纹理上取出图块产生一个CCSpriteFrame指针返回给变量frame1
local frame1 = CCSpriteFrame:frameWithTexture(textureDog, rect)
    --从frame0产生一个精灵返回给变量spriteDog(在C++中是CCSprite指针)
local spriteDog = CCSprite:spriteWithSpriteFrame(frame0)
--设置初始化时
spriteDog.isPaused = false
--设置精灵的位置在左上的位置
    spriteDog:setPosition(0, winSize.height / 4 * 3)

    --生成一个设定大小为2的CCMutableArray类的实例对象。用来存储CCSpriteFrame指针,将其指针返回给变量animFrames
local animFrames = CCMutableArray_CCSpriteFrame__:new(2)
--调用addObject将frame0和frame1放入animFrames
    animFrames:addObject(frame0)
    animFrames:addObject(frame1)
    --由容器类实例对象的指针animFrames创建一个动画帧信息对象,设定每0.5秒更新一帧,返回动画帧信息对象指针给变量animation
local animation = CCAnimation:animationWithFrames(animFrames, 0.5)
--由animation创建出一个动画动作,将这个动画动作的指针给变量animate
local animate = CCAnimate:actionWithAnimation(animation, false);
--设置精灵循环运行这个动作
    spriteDog:runAction(CCRepeatForever:actionWithAction(animate))

    -- 每帧移动松鼠
local function tick()
	   --如果松鼠停止动作,则返回
        if spriteDog.isPaused then return end
        --取得松鼠的位置
        local x, y = spriteDog:getPosition()
	    --如果松鼠的x值已经超出屏幕宽度将x位置变为0,否则加1,这样可以实现不断的往右移动,超出后就又回到最左边
        if x > winSize.width then
            x = 0
        else
            x = x + 1
        end
        --重新设置松鼠位置
        spriteDog:setPositionX(x)
    end
    --这里设置每帧调用上面的函数tick
    CCScheduler:sharedScheduler():scheduleScriptFunc(tick, 0, false)
    --返回松鼠精灵
    return spriteDog
end

-- 创建农场
local function createLayerFram()
	--创建一个新的Layer实例对象,将指针返回给变量layerFarm
    local layerFarm = CCLayer:node()

    -- 由“farm.jpg”创建一个精灵实例,将指针返回给变量bg
    local bg = CCSprite:spriteWithFile("farm.jpg")
---设置这个精灵实例的位置
bg:setPosition(winSize.width / 2 + 80, winSize.height / 2)
----将精灵放入新创建的Layer中
    layerFarm:addChild(bg)

    --在农场的背景图上的相应位置创建沙地块,在i从0至3,j从0至1的双重循环中,共创建了8块沙地块。
    for i = 0, 3 do
        for j = 0, 1 do
		   --创建沙地块的图片精灵
            local spriteLand = CCSprite:spriteWithFile("land.png")
		   --设置精灵的位置,在j的循环中每次向右每次增加180个位置点。在i的循环中每次会跟据i与2取模的结果向左移90个位置点,向上移95的一半数量的位置点。这样最先绘制最下面的两个沙地块,再绘制上面两个。再上面两个直至最上面两个。注意:这里的位置计算数值不必太纠结,如果是依照land.png的图片大小182x94,则这里改成spriteLand:setPosition(200 + j * 182 – (i % 2) * 182 / 2, 10 + i * 94 / 2)会更好理解一些。
            spriteLand:setPosition(200 + j * 180 - i % 2 * 90, 10 + i * 95 / 2)
		   --将沙地块的图片精录放入到新创建的Layer中
            layerFarm:addChild(spriteLand)
        end
    end

    -- 使用CCTextureCache:sharedTextureCache()取得纹理块管理器,将dog.png放入纹理块管理器产生一张纹理textureCrop
local textureCrop = CCTextureCache:sharedTextureCache():addImage("crop.png")
-- 由一个矩形从纹理取出一个图块frameCrop
local frameCrop = CCSpriteFrame:frameWithTexture(textureCrop, CCRectMake(0, 0, 105, 95))
-- 和刚才的沙地块一样,由图块创建出精灵并放在相应的位置上,这里不再赘述。
    for i = 0, 3 do
        for j = 0, 1 do
            local spriteCrop = CCSprite:spriteWithSpriteFrame(frameCrop);
            spriteCrop:setPosition(10 + 200 + j * 180 - i % 2 * 90, 30 + 10 + i * 95 / 2)
            layerFarm:addChild(spriteCrop)
        end
    end

    -- 调用createDog增加一个移动的松鼠精灵
local spriteDog = creatDog()
-- 将松鼠精录放入新创建的Layer中
    layerFarm:addChild(spriteDog)

    -- 定义变量touchBeginPoint,设为未使用
    local touchBeginPoint = nil
	--定义当按下屏幕时触发的函数
local function onTouchBegan(x, y)
	   --打印位置信息
        cclog("onTouchBegan: %0.2f, %0.2f", x, y)
        --将x,y存在变量touchBeginPoint中
        touchBeginPoint = {x = x, y = y}
	   --暂停精灵spriteDog的运动
        spriteDog.isPaused = true
        --返回true
        return true
    end
	--定义当保持按下屏幕进行移动时触发的函数
local function onTouchMoved(x, y)
	   --打印位置信息
        cclog("onTouchMoved: %0.2f, %0.2f", x, y)
        --如果touchBeginPoint有值
        if touchBeginPoint then
		   --取得layerFarm的位置,将返回结果存放在cx和cy中。
            local cx, cy = layerFarm:getPosition()
		   --设置layerFarm的位置受到按下移动的偏移影响
            layerFarm:setPosition(cx + x - touchBeginPoint.x,
                                  cy + y - touchBeginPoint.y)
		   --更新当前按下位置存放到变量touchBeginPoint中
            touchBeginPoint = {x = x, y = y}
        end
    end
    --当离开按下屏幕时
local function onTouchEnded(x, y)
	   --打印位置信息
        cclog("onTouchEnded")
        --将变量touchBeginPoint设为未用
        touchBeginPoint = nil
	   --将变量spriteDog
        spriteDog.isPaused = false
    end
    --响应按下事件处理函数
local function onTouch(eventType, x, y)
	    --如果是按下时,调用onTouchBegan
        if eventType == CCTOUCHBEGAN then
            return onTouchBegan(x, y)
        --如果是按下并移动时,调用onTouchMoved
        elseif eventType == CCTOUCHMOVED then
            return onTouchMoved(x, y)
        --松开时,调用onTouchEnded
        else
            return onTouchEnded(x, y)
        end
    end
--调用layerFarm的registerScriptTouchHandler函数注册按下事件的响应函数
layerFarm:registerScriptTouchHandler(onTouch)
--调用layerFarm的setIsTouchEnabled使layerFarm能够响应屏幕按下事件
    layerFarm:setIsTouchEnabled(true)
    --返回layerFarm
    return layerFarm
end


-- 定义创建菜单层函数
local function createLayerMenu()
    --创建一个新Layer,将其指针返回给变量layerMenu
    local layerMenu = CCLayer:node()
	--定义三个本地变量
    local menuPopup, menuTools, effectID
    --定义本地函数menuCallbackClosePopup
    local function menuCallbackClosePopup()
        -- 通过参数effectID关闭指定声音
        SimpleAudioEngine:sharedEngine():stopEffect(effectID)
        --设置menuPopup不显示
        menuPopup:setIsVisible(false)
    end
    --定义本地函数menuCallbackOpenPopup
    local function menuCallbackOpenPopup()
        -- 循环播放声音文件“effect1.wav”,并返回对应的声音ID给变量effectID
        effectID = SimpleAudioEngine:sharedEngine():playEffect("effect1.wav")
        -- 设置menuPopup显示
        menuPopup:setIsVisible(true)
    end

    -- 创建图片菜单按钮,设置其两个状态(普通和按下)的图片都相同是menu2.png,返回图片菜单按钮给menuPopupItem
local menuPopupItem = CCMenuItemImage:itemFromNormalImage("menu2.png", "menu2.png")
-- 设置图片菜单按钮的位置在0,0点
menuPopupItem:setPosition(0, 0)
-- 为图片菜单按钮注册响应函数menuCallbackClosePopup
menuPopupItem:registerScriptHandler(menuCallbackClosePopup)
-- 由图片菜单按钮menuPopupItem创建出菜单返回给变量menuPopup
menuPopup = CCMenu:menuWithItem(menuPopupItem)
-- 设置菜单menuPopup的位置为屏幕中央
    menuPopup:setPosition(winSize.width / 2, winSize.height / 2)
    --设置menuPopup不显示。
menuPopup:setIsVisible(false)
--将菜单放入layerMenu中
    layerMenu:addChild(menuPopup)

-- 下面几行代码创建左下角的图片菜单按钮menuToolsItem及菜单menuTools,与上面的代码基本相似,不再赘述。
local menuToolsItem = CCMenuItemImage:itemFromNormalImage("menu1.png", "menu1.png")
menuToolsItem:setPosition(0, 0)
menuToolsItem:registerScriptHandler(menuCallbackOpenPopup)
menuTools = CCMenu:menuWithItem(menuToolsItem)
menuTools:setPosition(30, 40)
    layerMenu:addChild(menuTools)
    --返回layerMenu
    return layerMenu
end


-- 注意:以上大部分都是函数的定义,以下才是真正的游戏逻辑。我在这里加个序号方便大家读懂。
-- 1。取得声音引擎的实例对象并调用其playBackgroundMusic函数加载并循环播放声音文件“background.mp3”。这里做为背景音乐
SimpleAudioEngine:sharedEngine():playBackgroundMusic("background.mp3", true);
-- 2。取得声音引擎的实例对象并调用其preloadEffect函数将声音文件“effect1.wav”预加载进内存。这里并不播放,预加载是为了在播放时不造成卡顿感。
SimpleAudioEngine:sharedEngine():preloadEffect("effect1.wav");

-- 3。创建一个场景返回给变量sceneGame
local sceneGame = CCScene:node()
-- 4。创建农场所用的Layer,并放入场景中
sceneGame:addChild(createLayerFram())
-- 5。创建菜单所用的Layer,并放入场景中
sceneGame:addChild(createLayerMenu())
-- 6。调用显示设备的单件实例对象的runWithScene函数运行场景sceneGame
CCDirector:sharedDirector():runWithScene(sceneGame)


       hello.lua读完了,我来做一下总结:

hello.lua定义了几个函数(1) . creatDog:创建松鼠动画精灵,并设置精灵每帧触发tick函数由左向右循环移动。 (2).createLayerFram:创建农场所用的Layer,然后使用双循环创建沙地块图片精灵和农作物图片精灵,并放在合适的位置构建出农场田园的景色。之后调用createDog。最后设定这个农场所用的Layer在接收到按下,按下移动和松开事件时响应的函数。(3).createLayerMenu:创建菜单所用的Layer及菜单,游戏共使用了两个菜单,第一个菜单是一个图片菜单按钮,处于屏幕中央,图片中是一些游戏工具按钮的内容,默认这个菜单不显示。定义这个菜单在按下时响应的函数是隐藏它自已。第二个菜单也是一个图片菜单按钮,处于屏幕左下角,默认这个菜单显示,定义这个菜单在按下时响应的函数功能为显示第一个菜单。游戏的逻辑在脚本文件的最后面。先加载播放背景音乐,之后创建场景并创建农场和菜单将各自返回的层放入场景,最后运行这个场景。

       我们已经知道,lua文件可以调用静态C函数,也可以通过tolua++访问类对象的成员函数进行游戏逻辑的处理。通过这个示例的分析,我们更加感受到了Lua脚本引擎的强大功能。

   

       做为一个负责任的程序员,虽然我很开心Cocos2d-x提供了Lua的脚本引擎,但是我发现在当前版本中有一个美中不足的问题。就是内存泄漏,如果你现在猛的一紧,哈哈,那就赶快来跟着我往下面进行吧。我们仍然回到HelloLua工程的AppDelegate.cpp:

AppDelegate::AppDelegate()
{
	// 引擎作者在这里屏蔽了CRT对于内存泄漏的检查
	//_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
}

我们将前面的注释去掉,编译运行。可以发现在输出栏里有大量的内存泄漏:

Cocos2d-x 的“HelloLua” 深入分析

我们姑切往后看。

AppDelegate::~AppDelegate()
{
	//释放声音引擎
	SimpleAudioEngine::sharedEngine()->end();
	//这里本来是释放脚本引擎所用到的内存的,但奇怪的是作者把它注释掉了
        // CCScriptEngineManager::purgeSharedManager();
}


       我暂且大着胆子把释放脚本引擎这一句的注释去掉,编译运行吧。发现关闭程序后在CCObject.cpp中会崩溃掉。

Cocos2d-x 的“HelloLua” 深入分析


     看一下堆栈,可以发现是在显示设备析构时进入基类析构造成的。我们可以在CCDirector和AppDelegate的析构里加断点来运行一下程序,可以看到在关闭程序后,是先执行AppDelegate的析构,后执行CCDirector的折构。因为在AppDelegate的析构中我们执行了对脚本引擎的调用。所以在CCObject的析构中。CCScriptEngineManager::shareManager()->getScriptEngine()返回的是个空指针,而空指针再调用removeCCObjectByID,那可不必崩嘛!好了,知道原理了,修改吧。


   if (m_nLuaID)
    {
		CCScriptEngineManager*	tpScriptEngineManage = CCScriptEngineManager::sharedManager() ;
		if(tpScriptEngineManage)
		{
			CCScriptEngineProtocol*	tpEngineProtocol = tpScriptEngineManage->getScriptEngine();
			if(tpEngineProtocol)
			{
				tpEngineProtocol->removeCCObjectByID(m_nLuaID);
			}

		}
		
		//CCScriptEngineManager::sharedManager()->getScriptEngine()->removeCCObjectByID(m_nLuaID);
    }


再次运行程序,关闭后可以看到内存泄漏的数量大幅减少。

Cocos2d-x 的“HelloLua” 深入分析


      仍然有一些泄漏,先来判断一下这些泄漏的出处吧,既然是Lua引擎的使用示例,跟Lua多少可能有关,我们打开hello.lua,将文件开头用“- -[[” 结尾用“]] – -” 将整篇注释。之后运行程序,哈哈,在输出窗口没有提示任何内存泄漏。这就说明内存泄漏与Lua引擎有一定关系。好吧,我们一点一点打开hello.lua中的语句。

       将“- – [[”移到“local winSize”之前,运行,仍然没有任何内存泄漏,证明上面的语句都不会有问题,再移到下一行,运行。哦哦~,出现了!4字节的内存泄漏。

Delete CriticalSection spin count Detected memory leaks!
Dumping objects ->
{12911} normal block at 0x00481AD0, 4 bytes long.
 Data: <    > 00 00 00 00 
Object dump complete.
The program '[8684] HelloLua.exe: Native' has exited with code 0 (0x0).


可以肯定的是在这一行Lua脚本引擎一定申请了4字节的内存。

local winSize = CCDirector:sharedDirector():getWinSize()


       这里面调用了两个函数,第一个是sharedDirector(),第二个是getWinSize()。我们先来看getWinSize()是否有内存申请。打开CCDirector.cpp中找到getWinSize函数加入断点。运行程序,中断后我们观察其调用堆栈,正好是Lua脚本调用。

Cocos2d-x 的“HelloLua” 深入分析


       继续按F10运行,进入Lua调用的静态函数

Cocos2d-x 的“HelloLua” 深入分析


       我们可以看到在这个函数中能过Mtolua_new动态态建了一个CCSize实例对象,返回其指针给tolua_obj,我们进入Mtolua_new,就是new函数了,这里是确定申请了内存的。大小为8字节。我们继续运行,下一句是告知Lua这个数据指针的名称是CCSize,在Cocos2d-x引擎中,对于Lua脚本引擎是分别与Cocos2d-x中的各种类做了绑定。通过这个名称是可以识别到的。继续下一句tolua_register_gc(tolua_S,lua_gettop(tolua_S));这一行其实是将指针数据注册到lua的拉圾收集器,由拉圾收集器负责处理。我们现在打开LuaCocos2d.cpp的最上面,会发现有一些供lua调用的静态函数,它们均以tolua_collect做函数名称的前缀,其意义是为了释放相应类形的内存块。我们找到下面一段:

static int tolua_collect_CCSize (lua_State* tolua_S)
{
     CCSize* self = (CCSize*) tolua_tousertype(tolua_S,1,0);
	Mtolua_delete(self);
	return 0;
}


     顾名思义,这个函数对CCSize类型的指针进行delete,我们在这里加入断点。然后再启动程序。当我们关闭程序时,会断在这里。

 

Cocos2d-x 的“HelloLua” 深入分析


       看一下调用堆栈,很明显,在Lua脚本引擎析构时进行了拉圾清空处理,这里可以看一下tolua_tousertype(tolua_S,1,0)返回的self的值,没错,是我们使用的CCSize。所以这里可以确定getWinSize是没有问题的。那4字节的内存泄漏应该是sharedDirecotor()函数造成的。

       我们在CCDirector.cpp中找到sharedDirector()函数,加断点运行。略过前20次中断,在第21次中断时,我们可以看到函数调用堆栈的情况跟lua有关,也就是上一句Lua在调用C函数触发的。这里已经是相当底层了,做为入门教程,我并不打算再对外有的内存进行一步一步的跟踪排查了。我们还是寄希望作者会在后面的版本中修复内存泄漏的问题吧。好了。有点累了,玩去~


分类: cocos2d, cocos2d-lua 标签:

How to set background color of layer in cocos2d-x?

2012年8月24日 没有评论

I’ve been writing a game using cocos2d-x and ran into an issue with changing the background color. I found an example in cocos2d, but apparently this only applies to cocos2d which is written in Obj-c. Basically the idea is to use a CCLayerColor instead of CCLayer, and when the constructor gets fired set the color.

Does anyone know how to change the background color in cocos2d-x? Seems like it would be pretty simple, I’m pretty sure I’m missing something obvious.

In 2.X or below,
extends CCColorLayer instead of CCLayer.

class CommonScene : public cocos2d::CCLayerColor
{
public:
...
}

init with this code..

bool CommonScene::init()
{
    //////////////////////////////
    // 1. super init first
    if( !CCLayerColor::initWithColor(ccc4(255, 255, 255, 255)) ) //RGBA
    {
        return false;
    }
    ...
}

and if you want to change background use setColor

this->setColor(ccc3(255, 255, 255));

will work charm.




Updated:
in 3.0 or above. modify above code like this.

h,

class CommonScene : public cocos2d::LayerColor

cpp,

if( !LayerColor::initWithColor(Color4B(255,255,255,255)) )

For Cocos2d-x v3.0

In *.h

class PlayScene : public cocos2d::LayerColor

In *.cpp

bool PlayScene::init()
{
    if ( !LayerColor::initWithColor(Color4B(255, 255, 255, 255) )) {
        return false;
    }

    return true;
}

The easiest way I could locate that does not impact performance, is to simply do:

glClearColor(1.0,1.0,1.0,1.0);

Somewhere in your Scene init() function. This way you do not have to change to a LayerColor and performance is not affected either. Cheers!

In Cocos2d 3.3 usually I put it in INIT or ENTER method:

[self setColor: [CCColor redColor]];

For custom RGB values use:

ccColor3B yellow = {255,255,0};
CCColor *c = [CCColor colorWithCcColor3b:yellow];
[self setColor:c];
分类: cocos2d, stackoverflow精选 标签:

Cocos2d-X游戏工具开发之一:将Cocos2d-X嵌入MFC的子窗体方法讲解

2012年8月22日 没有评论

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier]  

红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x

另本章为我的Cocos2d-x教程一书初稿。望各位看官多提建议!

        本节所用Cocos2d-x版本:cocos2d-1.0.1-x-0.12.0

        我在之前的“如何利用Cocos2d-x开发一个游戏?”。在实际的项目开发过程中,工具是支撑开发流程规范的必要条件。一套好的工具可以使工作流程顺畅。从而极大的提高效率。我们知道Unity3d场景编辑器就是一个很好的例子。它将游戏开发中的各个流程节点的工作都集成到编辑器中,这是一个很COOL的想法。对于游戏开发团队来说,只需要掌握好这个流程,使用编辑器做好每一步的编辑工作就可以快速开发出高品质的游戏。

       但对于Cocos2d-x来说。虽然网络上有一些开放的工具,但零零散散的不成系统。并不方便使用。这一章来简要介绍一下如何使用MFC来进行Cocos2d-x相关工具的开发。请对MFC不熟悉的朋友先学习一下基本的对话框程序再进行本章内容的学习。

       第一节:将Cocos2d-x嵌入MFC的子窗体中  

    [注:学习这一章之前请先学习Cocos2d-x 的“HelloWorld” 深入分析一文]

       首先,我们用VC++在Cocos2d-x的目录里建立了个Unicode字符集MFC对话框程序。这里命名为Cocos2dXEditor。按照HelloWorld工程设置把包含头文件目录,库文件目录,包含库都设置好。并且画好对话框界面

     如图:

Cocos2d-X游戏工具开发之一:将Cocos2d-X嵌入MFC的子窗体方法讲解

         我把界面设计为三部分,左边和右边用来创建对话框面板,至于要具体显示什么根据我们的工具开发需求而定。暂时先不管。而中间放一个Picture 控件,做为Cocos2d-x的显示窗口。为了生成相应的窗口控件变量,我们需要将此Picture控件的ID改成自定义的一个ID,这里我们改成IDC_COCOS2DXWIN保存。

         我们画好界面后,选中Picture控件后右键单击,在弹出菜单中找到“添加变量”。为Picture控件生成一个控件变量。这里命名为m_Cocos2dXWin,并点击完成。

                                       Cocos2d-X游戏工具开发之一:将Cocos2d-X嵌入MFC的子窗体方法讲解

好,现在主对话框类里有了这么一句:

public:
         CStatic      m_Cocos2dXWin;

       这个变量是CStatic类型的,它只是一个最简单的CWnd派生类。并不能显示Cocos2d-x。我们需要做点工作来创建一个新的类来替代它完成显示Cocos2d-x的工作。

       在工程右键弹出菜单里找到“添加”一项,在其子菜单中点击“类”。

                                                       Cocos2d-X游戏工具开发之一:将Cocos2d-X嵌入MFC的子窗体方法讲解

在弹出的“添加类”对话框中“MFC”项中的“MFC类”。

                                  Cocos2d-X游戏工具开发之一:将Cocos2d-X嵌入MFC的子窗体方法讲解

         点击“添加”。这时会弹出“MFC类向导”对话框。我们在类名里输入“CCocos2dXWin”,并选择基类CWnd。然后点击“完成”。

                                       Cocos2d-X游戏工具开发之一:将Cocos2d-X嵌入MFC的子窗体方法讲解

         向导会为我们的工程自动加入两个文件“Cocos2dXWin.h”和“Cocos2dXWin.cpp”。这才是我们要进行Cocos2d-x显示的窗体类,它的基类是CWnd,与Picture控件有相同的基类。

 

        打开Cocos2dXWin.h,在CCocos2dXWin类中增加一个public成员函数声明:

//创建Cocos2dX窗口
BOOL        CreateCocos2dXWindow();

        我们另外增加一个private变量用来标记窗口是否已经进行了Cocos2d-x的OpenGL窗口创建。

private:
	//是否已经初始化
	BOOL				m_bInitCocos2dX;

          在CPP文件中加入需要用到的头文件

#include "CCEGLView_win32.h"
#include "../Classes/AppDelegate.h"
#include "cocos2d.h"

          下面来手动增加函数定义

/创建Cocos2dX窗口
BOOL	CCocos2DXWin::CreateCocos2dXWindow()
{
	//新建一个CRect变量获取窗口的客户区大小
	CRect	tClientRect;
	GetClientRect(&tClientRect);
	//调用cocos2d中的CCApplication::sharedApplication()获取Cocos2d-x程序类单件实例对象。调用其run函数。在这里我们传入四个参数,与第一章“HelloWorld”不同的是这里增加了新参数“当前窗口的句柄”。这是使用MFC来嵌入Cocos2d-x窗口的关健所在。我们暂时埋个伏笔,后面再详解。
	cocos2d::CCApplication::sharedApplication().run(GetSafeHwnd(),TEXT("第一个Cocos2d-x程序"),tClientRect.Width(),tClientRect.Height());
	//这里将变量设置为TRUE
	m_bInitCocos2dX = TRUE;
	return TRUE;
}

         现在还不能运行。因为CCApplication的run函数跟本就没有HWND参数,我们必须对CCApplication动个小手术。使它能够接收到窗体的句柄并使用这个句柄来创建OpenGL。

       

         找到CCApplication_win32.cpp中的run函数做以下修改:

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier]  

int CCApplication::run(HWND hWnd,LPCTSTR szTitle,UINT wWidth,UINT wHeight)
{
    PVRFrameEnableControlWindow(false);

    // Main message loop:
    MSG msg;
    LARGE_INTEGER nFreq;
    LARGE_INTEGER nNow;

    QueryPerformanceFrequency(&nFreq);
   //将原临时变量nLast改为成员变量m_nLast。因为后面要把每帧的渲染做到外部调用的public函数里,所以需要存一下上帧的时间。
    QueryPerformanceCounter(&m_nLast);

    // 关键点1:必须将参数hWnd也做为参数传给initInstance函数。
    if (! initInstance(hWnd,szTitle,wWidth,wHeight) || ! applicationDidFinishLaunching())
    {
        return 0;
    }

    CCEGLView& mainWnd = CCEGLView::sharedOpenGLView();
	//关键点2:我们如果要使用指定的CWnd窗口来创建OpenGL,那么必须放弃使用Cocos2d-x封装好的消息循环。因为这些工作都会交由MFC来做。我们为了不破坏原来的Cocos2d-x创建窗口方式。就在这里做个判断处理,如果要使用原来由Cocos2d-x创建窗口的方式,只需要第一个参数传NULL就好了。
    if(NULL == hWnd)
	{
		mainWnd.centerWindow();
		ShowWindow(mainWnd.getHWnd(), SW_SHOW);

		while (1)
		{
			if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
			{
				// Get current time tick.
				QueryPerformanceCounter(&nNow);

				// If it's the time to draw next frame, draw it, else sleep a while.
				if (nNow.QuadPart - m_nLast.QuadPart > m_nAnimationInterval.QuadPart)
				{
					m_nLast.QuadPart = nNow.QuadPart;
					CCDirector::sharedDirector()->mainLoop();
				}
				else
				{
					Sleep(0);
				}
				continue;
			}

			if (WM_QUIT == msg.message)
			{
				// Quit message loop.
				break;
			}

			// Deal with windows message.
			if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}

		return (int) msg.wParam;
	}
	return 0;
}

           修改.h中的函数声明与cpp一致并保存,然后打开CCApplication的派生类AppDelegateinitInstance函数。做同样的修改工作。

#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
		CCEGLView * pMainWnd = new CCEGLView();
		CC_BREAK_IF(! pMainWnd
			|| ! pMainWnd->Create(hWnd,szTitle, wWidth, wHeight));
			//|| ! pMainWnd->Create(TEXT("cocos2d: Hello World"), 480, 320));
#endif  // CC_PLATFORM_WIN32

           修改.h中函数声明与cpp保持一致,再打开CCEGL_View_win32.cpp,找到Create函数,继续手术:

bool CCEGLView::Create(HWND hWnd,LPCTSTR pTitle, int w, int h)
{
	bool bRet = false;
	do 
	{
		//在这里做个判断,如果参数中的窗口句柄不为空,则使用参数句柄做为成员变量m_hWnd的值。而在
		if(hWnd)
		{
			m_hWnd = hWnd ;
		}
		else
		{
			CC_BREAK_IF(m_hWnd);

			HINSTANCE hInstance = GetModuleHandle( NULL );
			WNDCLASS  wc;		// Windows Class Structure

			// Redraw On Size, And Own DC For Window.
			wc.style          = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;  
			wc.lpfnWndProc    = _WindowProc;					// WndProc Handles Messages
			wc.cbClsExtra     = 0;                              // No Extra Window Data
			wc.cbWndExtra     = 0;								// No Extra Window Data
			wc.hInstance      = hInstance;						// Set The Instance
			wc.hIcon          = LoadIcon( NULL, IDI_WINLOGO );	// Load The Default Icon
			wc.hCursor        = LoadCursor( NULL, IDC_ARROW );	// Load The Arrow Pointer
			wc.hbrBackground  = NULL;                           // No Background Required For GL
			wc.lpszMenuName   = NULL;                           // We Don't Want A Menu
			wc.lpszClassName  = kWindowClassName;               // Set The Class Name

			CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError());		

			// center window position
			RECT rcDesktop;
			GetWindowRect(GetDesktopWindow(), &rcDesktop);

			// create window
			m_hWnd = CreateWindowEx(
				WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,	// Extended Style For The Window
				kWindowClassName,									// Class Name
				pTitle,												// Window Title
				WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX,		// Defined Window Style
				0, 0,								                // Window Position
				0,                                                  // Window Width
				0,                                                  // Window Height
				NULL,												// No Parent Window
				NULL,												// No Menu
				hInstance,											// Instance
				NULL );

			CC_BREAK_IF(! m_hWnd);
		}
        m_eInitOrientation = CCDirector::sharedDirector()->getDeviceOrientation();
        m_bOrientationInitVertical = (CCDeviceOrientationPortrait == m_eInitOrientation
            || kCCDeviceOrientationPortraitUpsideDown == m_eInitOrientation) ? true : false;
        m_tSizeInPoints.cx = w;
        m_tSizeInPoints.cy = h;
        resize(w, h);

		// 当调用CCEGL的静态函数create来创建OpenGL窗口时,传入的参数是CCEGLView实例对象,在内部会取得实例对象的m_hWnd变量做为OpenGL的窗口句柄。这样我们就实现了使用参数传递的窗口句柄来创建OpenGL窗口。
		m_pEGL = CCEGL::create(this);

		if (! m_pEGL)
		{
			DestroyWindow(m_hWnd);
			m_hWnd = NULL;
			break;
		}

		s_pMainWindow = this;
		bRet = true;
	} while (0);

	return bRet;
}

         这样我们就完成了对于创建窗口函数的修改。但是因为屏蔽了原窗口的创建,所以也同时屏蔽了Cocos2d-x对于窗口消息的处理。我们在类视图中找到CCocos2dXWin,在右键弹出菜单中点击“属性”。打开类属性编辑框。

                                             Cocos2d-X游戏工具开发之一:将Cocos2d-X嵌入MFC的子窗体方法讲解

           在WindowProc一栏中增加函数WindorProc,完成后进入CCocos2dXWin的WindorProc函数,因为我们在CCEGLView的Create函数中可以找到在注册窗口类时,其设定的窗口消息处理回调函数是WindowProc.而其实例对象是单件。故可以这样修改:

LRESULT CCocos2DXWin::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	// 这里我们先判断一下是否初始化完成Cocos2dX再进行消息处理
	if(m_bInitCocos2dX)
	{
		CCEGLView::sharedOpenGLView().WindowProc(message, wParam, lParam);
	}
	return CWnd::WindowProc(message, wParam, lParam);
}

         这样Cocos2dXWin所实例化的窗口就可以接收到鼠标等对于窗口的消息了。但是!我们屏幕了Cocos2d-x的消息循环,而Cocos2d-x的渲染处理函数是在消息循环中调用的。我们就算现在运行程序,也看不到帅气的小怪物。所以我们必须想办法实时的调用Cocos2d-x的渲染处理函数。对于工具来说,我们并不需要考虑太高的FPS刷新帧数,所以我们只需要使用一个定时器,并在定时触发函数中进行Cocos2d-x的渲染处理函数的调用就可以了。

        在CCocos2DXWin::CreateCocos2dXWindow()函数尾部加入一句:

         

SetTimer(1,1,NULL);

         加入一个ID为1的定时器,设置它每1毫秒响应一次,其处理函数为默认,则使用CCocos2DXWin的WINDOWS消息处理函数对于WM_TIMER进行响应就可以了。

         按之前增加WindorProc函数的方式找到CCocos2DXWin的消息WM_TIMER一项,增加函数OnTimer.如图:

                                       Cocos2d-X游戏工具开发之一:将Cocos2d-X嵌入MFC的子窗体方法讲解

          在其生成的函数OnTimer中我们新写一个函数调用,则每次定时响应调用

void CCocos2DXWin::OnTimer(UINT_PTR nIDEvent)
{
	//我们写一个renderWorld函数代表Cocos2d-x的世界渲染
	cocos2d::CCApplication::sharedApplication().renderWorld();
	CWnd::OnTimer(nIDEvent);
}

         我们打开CCApplication_win32.h,为CCApplication类增加一个public的renderWorld函数。

//帧循环调用渲染
	bool	renderWorld();

        在cpp中我们将消息循环中的相关处理移入进来

bool CCApplication::renderWorld()
{   
	LARGE_INTEGER nNow;
	// Get current time tick.
	QueryPerformanceCounter(&nNow);

	// If it's the time to draw next frame, draw it, else sleep a while.
	if (nNow.QuadPart - m_nLast.QuadPart > m_nAnimationInterval.QuadPart)
	{
		m_nLast.QuadPart = nNow.QuadPart;
		CCDirector::sharedDirector()->mainLoop();
		return true;
	}
	return false;
}

       OK,这样我们就可以在外部来调用Cocos2d-x的显示设备进行渲染处理了。当然,我们也不能忘了在CCocos2dXWin窗口销毁时把定时器资源进行一下释放。找到类消息WM_DESTROY新增函数OnDestroy。

                                      Cocos2d-X游戏工具开发之一:将Cocos2d-X嵌入MFC的子窗体方法讲解

void CCocos2DXWin::OnDestroy()
{
	//在Cocos2d-x的引擎文件CCEGLView_win32.cpp中CCEGLView::release()会再次发送调用DestroyWindow,所以这里用变量m_bInitCocos2dX做下判断,避免二次销毁
	if(TRUE == m_bInitCocos2dX)
	{
		//退出将m_bInitCocos2dX设为FALSE
		m_bInitCocos2dX = FALSE;
		//释放定时器资源
		KillTimer(1);
		CWnd::OnDestroy();
	}
}

         我们在“HelloWorld”的代码分析中可以知道在Cocos2d-x程序退出时先后响应WM_CLOSE和WM_DESTROY。在CCEGLView::WindowProc 可以看到

	case WM_CLOSE:
		CCDirector::sharedDirector()->end();
		break;

	case WM_DESTROY:
		PostQuitMessage(0);
		break;

         而CCDirector的end函数并不是立即执行停止,他设置成员变量m_bPurgeDirecotorInNextLoop为true,并在下一帧mainLoop循环时调用purgeDirector()函数进行显示设备和场景的最终释放。所以我们要想在窗口退出时释放Cocos2d-x显示设备和场景,必须做一下相关处理。理解了这一点。其实就很简单了。

        只需要在CCocos2DXWin::OnDestroy()中增加两句代码即可:

void CCocos2DXWin::OnDestroy()
{
	…
	KillTimer(1);
	//调用显示设备单件实例对象的end函数
	CCDirector::sharedDirector()->end();
	//处理一次下一帧,必须调用.
	CCDirector::sharedDirector()->mainLoop();
	CWnd::OnDestroy();
	}
}

       OK,CCocos2DXWin类基本完成。现在在Cocos2dXEditorDlg.h中将

      

CStatic            m_Cocos2dXWin;

      改为

       

CCocos2DXWin       m_Cocos2dXWin;

     将CCocos2DXWin的头文件包含进来,然后在控件初始化(比如OnInitDialog函数)中调用一下m_Cocos2dXWin.CreateMainCoco2dWindow()。编译成功后就算完成了将Cocos2d-x嵌入MFC的CWnd控件的过程。不过,您需要在窗口大小变化时对这个控件重设位置及大小以使窗口始终上下充满空间。这些属于MFC的基础知识,在此不再赘述。

       最后运行一下看下成果吧。下面是我运行的结果(:左边面板稍加制做~):

Cocos2d-X游戏工具开发之一:将Cocos2d-X嵌入MFC的子窗体方法讲解

分类: cocos2d, cocos2d-x 标签:

如何利用Cocos2d-x开发一个游戏?

2012年8月21日 没有评论
[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier]
红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x
 
     如何利用Cocos2d-x开发一个游戏?
  这个问题的结果应该是一个流程。我将从一些长期的PC端游戏开发经验结合Cocos2d-x的功能来进行说明。
    首先,先确定游戏策划案。确定策划案则确定了游戏的规模和所需要的模块。而所需要的模块则带来对于数据的需求,数据的需求则引出配置文件以及编辑工具的需求。
然后,确定所使用到的工具以及要开发的工具。通常来说,一个2D的RPG或ARPG类型的游戏会可能用到的工具主要有以下几种,它们按照使用的顺序应该是:
(1)图元素管理器:负责图片的编辑,生成块切割数据。
(2)动画编辑器:负责图元素的导入,生成动画。保存动画数据。
(3)特效编辑器:负责图元素与动画的导入,生成各种粒子特效。保存特效数据。
(4)NPC编辑器:负责动画导入生成NPC动作,编辑NPC的位置锚点。动作中的事件或人物脚本。如果需要也可以在这里导入特效编辑技能。保存NPC相关数据。
(5)界面编辑器:  负责使用图元素和动画,特效进行界面的布局编辑与事件脚本函数。并将数据保存 
(6)场景编辑器:负责生成横纵向或45度视角格子地图。并使用图元素或特效进行场景的背景,地表,建筑等层编辑。还有将NPC布置在相应位置。
      “公欲善其事,必先利其器”。以上六种工具是成熟的开发团队所要必备的。对于一般个人开发的规模较小的游戏,这些工具并不是必须的。可以省略或使用一些网络上公开的工具。而要开发规模稍大点的游戏,如RPG类型的游戏,所需要的工具通常要自已进行有针对性的开发。而且不仅限于这些工具。
      当你具备了你需要的编辑工具之后,你需要将它们交付给美术人员使用。美术人员将做好的图片由图元素管理器进行切割。在动画编辑器里制做相应的动画。在特效编辑器里编辑出需要的特效。在NPC编辑器中创建出具有多个动画动作的多方向的NPC,编辑NPC的位置锚点。并跟据策划案确定所需要界面。使用界面编辑器编辑好它们的布局。同时根据策划案的关卡在场景编辑器里编辑出场景。
      美术人员在开动的同时。程序需要进行游戏框架和所需要的类,功能函数的设计。编写出一个可以运行的逻辑框架。
      然后,将各个工具导出的数据文件加载入框架。使它们显示出正确的场景,NPC,特效。
      最后,调试测试程序。修改BUG。完成WINDOWS版本的游戏。
 
      WINDOWS版本调试无误,移植到其它平台上编译运行。
 
如何利用Cocos2d-x开发一个游戏?
 
 注,NPC编辑器和界面编辑器是可以并行的。故都在第四步
 
                                                                              
分类: 未分类 标签:

Cocos2d-x 的“HelloWorld” 深入分析

2012年8月20日 没有评论

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier]  

红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x

本章为我的Cocos2d-x教程一书初稿。望各位看官多提建议!

 

                          Cocos2d-x 的“HelloWorld” 深入分析

 

        本节所用Cocos2d-x版本:cocos2d-1.0.1-x-0.12.0

        不能免俗,一切都从“HelloWorld!”开始.打开HelloWorld工程,里面有两个文件目录Classeswin32

Classes下有HelloWorldScene.h/cpp ,AppDelegate.h/cpp.

win32下有main.h/cpp

       首先看一下win32目录下的main.h,其中定义了使用win32平台进行编译的宏和一些Windows编程头文件。

 

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier] 
#ifndef __MAIN_H__

#define __MAIN_H__

//定义使用WIN32平台进行编译的宏

#define WIN32_LEAN_AND_MEAN            

// 所用到的Windows编程所用头文件

#include <windows.h>

#include <tchar.h>

// Cocos的C语言头文件

#include "CCStdC.h"

#endif// __MAIN_H__

 

再打开main.cpp.

 

//加入main.h头文件

#include "main.h"

//加入使用的AppDelegate类头文件

#include "../Classes/AppDelegate.h"

//WinMain主函数

int APIENTRY _tWinMain(HINSTANCE hInstance,

   HINSTANCE hPrevInstance,

   LPTSTR    lpCmdLine,

   int       nCmdShow)

{

//UNREFERENCED_PARAMETER用于在VC编译器下告知编译器,不必检测改变量是否使用的警告。

UNREFERENCED_PARAMETER(hPrevInstance);

UNREFERENCED_PARAMETER(lpCmdLine);

    // 创建一个Cocos2d-x程序实例

AppDelegate app;

    // 运行程序实例

    return cocos2d::CCApplication::sharedApplication().run();

}

 

         代码看着就这么几行,好像非常简单。那是因为Cocos2d-x把很多WINDOWS窗口程序的代码都封装到了内部。这也使得它更加简单易学。但我们也应该明白这里面是怎么一回事。

             

        咱们转到AppDelegate.h,可以看到AppDelegate类是一个私有继承Cocos2d命名空间中的CCApplication类。它的成员函数均是重载了CCApplication类的成员函数。顾名思义,CCApplication代表了程序类。我们打开它的声明文件看一下:

 

#ifndef __CC_APPLICATION_WIN32_H__

#define __CC_APPLICATION_WIN32_H__

//Windows头文件

#include <Windows.h> 

//Cocos2d-x公共头文件,声明了一些公共函数以及语言类型枚举ccLanguageType

#include "CCCommon.h"

//使用cocos2d的命名空间来包括后面的代码

NS_CC_BEGIN;

//声明一下CCRect类,在CCApplication中会用到CCRect类指针

class CCRect;

class CC_DLL CCApplication

{

public:

//构造

CCApplication();

//析构

    virtual ~CCApplication();

    //初始化

    virtual bool initInstance() = 0;

//程序启动后调用的函数,在这里创建设备与场景

    virtual bool applicationDidFinishLaunching() = 0;

//当程序转入后台,如电话打入时调用

    virtual void applicationDidEnterBackground() = 0;

//当程序转入前台,再次恢复时调用

    virtual void applicationWillEnterForeground() = 0;

//在“设备”设置FPS时调用的函数,设置帧间隔时间

void setAnimationInterval(double interval);

//声明一个枚举,列出当前设备的摆放方向

    typedef enum

    {

        /// 垂直方向, home 键在下面

        kOrientationPortrait = 0,

        /// 垂直方向, home 键在上面

        kOrientationPortraitUpsideDown = 1,

        /// 水平方向,home键在右边

        kOrientationLandscapeLeft = 2,

        /// 水平方向,home健在左边

        kOrientationLandscapeRight = 3,

    } Orientation;

    

//在“设备”改变了摆放方向后调用的函数,设置设备摆放方向

    Orientation setOrientation(Orientation orientation);

//取得窗口的状态栏所在的矩形位置

    void    statusBarFrame(CCRect * rect);

    //运行程序

    int run();

//取得当前的程序实例,这种用法可参考C++模式设计中的“单件”模式

    static CCApplication& sharedApplication();

//取得当前的语言类型

    static ccLanguageType getCurrentLanguage();

protected:

//程序实例句柄

HINSTANCE           m_hInstance;

//加速键句柄

HACCEL              m_hAccelTable;

//声明为帧间隔,实际上是每两帧之间的频率次数

    LARGE_INTEGER       m_nAnimationInterval;

//单件的程序实例指针

    static CCApplication * sm_pSharedApplication;

};

NS_CC_END;

#endif// __CC_APPLICATION_WIN32_H__

          通过对于CCApplication_win32.h的代码分析,我们可以清楚CCApplication的功能是对于程序的控制。

我们转到CCApplication类的cpp文件CCApplication_win32.cpp再来分析一下。

         读前小提示:  CCDirector代表显示设备管理器。也是一个单件类。通过其静态函数CCDirector::sharedDirector()来访问唯一的显示设备。

         重点关注函数:CCApplication(),run()

 

#include "CCApplication.h"
//设备头文件
#include "CCDirector.h"
//在注册表中写入对于PVRFrame的显示和隐藏的设置
static void PVRFrameEnableControlWindow(bool bEnable);
//使用cocos2d的命名空间来包括后面的代码
NS_CC_BEGIN;
// CCApplication的静态成员指针变量, 单件对象指针
CCApplication * CCApplication::sm_pSharedApplication = 0;
//构造函数
CCApplication::CCApplication()
: m_hInstance(NULL)
, m_hAccelTable(NULL)
{
//获取当前程序句柄
m_hInstance= GetModuleHandle(NULL);
//初始化m_nAnimationInterval
m_nAnimationInterval.QuadPart = 0;
//断言程序中只有一个sm_pSharedApplication。确保当前类只有一个实例对象
CC_ASSERT(! sm_pSharedApplication);
//设置单件对象指针指向当前类对象实例
    sm_pSharedApplication = this;
}
//析构
CCApplication::~CCApplication()
{
//断言程序只有一个sm_pSharedApplication就是指向当前类的实例对象
    CC_ASSERT(this == sm_pSharedApplication);
    sm_pSharedApplication = NULL;
}
//程序运行
int CCApplication::run()
{
    //设置注册表PVRFrame隐藏
    PVRFrameEnableControlWindow(false);
    //主消息循环
    MSG msg;
    LARGE_INTEGER nFreq;
    LARGE_INTEGER nLast;
    LARGE_INTEGER nNow;
//WINDOWS高精度定时器的用法,先获取频率
QueryPerformanceFrequency(&nFreq);
//获取当前的计数值,即频率x当前时间
    QueryPerformanceCounter(&nLast);
//initInstance函数为虚函数,由派生类AppDelegate进行了重载。此段代码在调用AppDelegate重载的initInstance函数之后调用applicationDidFinishLaunching函数完成一些初始化处理。
//注:AppDelegate重载initInstance函数做了什么我们暂且只先认为它如平时我们WINDOWS基本框架程序一样创建了一个Windows窗口。【伏笔1后面会有讲解】。
    if (! initInstance() || ! applicationDidFinishLaunching())
    {
        return 0;
    }
//取得当前使用的OPENGL窗口管理实例对象
CCEGLView& mainWnd = CCEGLView::sharedOpenGLView();
//将窗口居中显示
    mainWnd.centerWindow();
    ShowWindow(mainWnd.getHWnd(), SW_SHOW);
//非常熟悉!进入WINDOWS消息循环
    while (1)
{
   //如果没有获取到WINDOWS消息
        if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            // 取得当前的计数值,即频率x当前时间
            QueryPerformanceCounter(&nNow);
            //m_nAnimationInterval.QuadPart的值 为setAnimationInterval函数进行设置的固定值。此处是为了判断时间流逝了多久,是否应该更新显示设备
            if (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart)
            {
  //如果时间流逝达到了设定的FPS时间差,则更新计数值。
                nLast.QuadPart = nNow.QuadPart;
  //这里是设备渲染场景的函数,【伏笔2后面会有讲解】 
                CCDirector::sharedDirector()->mainLoop();
            }
            else
            {
  //sleep0秒的意义是让CPU做下时间片切换,防止死循环而使系统其它程序得不到响应。
                Sleep(0);
            }
            continue;
        }
   //有消息获取到
        if (WM_QUIT == msg.message)
        {
   // 如果获取的消息是退出则退出循环。
            break;
        }
        // 如果没有定义加速键或者处理完加速键信息
        if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
        {
   //处理Windows消息
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int) msg.wParam;
}
 
//外部调用的设置帧间隔时间
void CCApplication::setAnimationInterval(double interval)
{
    //获取高精度定时器的频率
    LARGE_INTEGER nFreq;
QueryPerformanceFrequency(&nFreq);
//计算出频率X帧间隔时差的值存入m_nAnimationInterval用做比较值
    m_nAnimationInterval.QuadPart = (LONGLONG)(interval * nFreq.QuadPart);
}
//摆放方向变化时外部自动调用的设置摆放方向
CCApplication::Orientation CCApplication::setOrientation(Orientation orientation)
{
    //切换OPENGL视窗的宽高
    CCEGLView * pView = CCDirector::sharedDirector()->getOpenGLView();
    if (pView)
    {
        return (Orientation)pView->setDeviceOrientation(orientation);
    }
    return (Orientation)CCDirector::sharedDirector()->getDeviceOrientation();
}
//获取状态栏的位置矩形
void CCApplication::statusBarFrame(CCRect * rect)
{
    if (rect)
    {
        // WINDOWS系统没有状态栏,所以返回的矩形各位置都是0
        *rect = CCRectMake(0, 0, 0, 0);
    }
}
//////////////////////////////////////////////////////////////////////////
// 静态成员函数,获取单件指针
//////////////////////////////////////////////////////////////////////////
CCApplication& CCApplication::sharedApplication()
{
    CC_ASSERT(sm_pSharedApplication);
    return *sm_pSharedApplication;
}
//静态成员函数,获取当前系统的语言类型
ccLanguageType CCApplication::getCurrentLanguage()
{
//默认为英语
    ccLanguageType ret = kLanguageEnglish;
//
LCID localeID = GetUserDefaultLCID();
unsigned short primaryLanguageID = localeID & 0xFF;
switch (primaryLanguageID)
{
case LANG_CHINESE://中文
ret = kLanguageChinese;
break;
case LANG_FRENCH://法文
ret = kLanguageFrench;
break;
case LANG_ITALIAN://意文
ret = kLanguageItalian;
break;
case LANG_GERMAN://德文
ret = kLanguageGerman;
break;
case LANG_SPANISH://西班牙文
ret = kLanguageSpanish;
break;
case LANG_RUSSIAN://俄文
ret = kLanguageRussian;
break;
}
    return ret;
}
NS_CC_END;
//////////////////////////////////////////////////////////////////////////
//在注册表中写入对于PVRFrame的显示和隐藏的设置
//////////////////////////////////////////////////////////////////////////
static void PVRFrameEnableControlWindow(bool bEnable)
{
    HKEY hKey = 0;
    // 打开注册表的 PVRFrame 项
    if(ERROR_SUCCESS != RegCreateKeyExW(HKEY_CURRENT_USER,
        L"Software//Imagination Technologies//PVRVFRame//STARTUP//",
        0,
        0,
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKey,
        NULL))
    {
        return;
    }
   
    const wchar_t * wszValue = L"hide_gui";
    const wchar_t * wszNewData = (bEnable) ? L"NO" : L"YES";
    wchar_t wszOldData[256] = {0};
DWORD   dwSize = sizeof(wszOldData);
//读取相应的键值
LONGstatus = RegQueryValueExW(hKey, wszValue, 0, NULL, (LPBYTE)wszOldData, &dwSize);
//如果键值不存在,或者键值存在但与当前值不同,重设键值
    if (ERROR_FILE_NOT_FOUND == status              
        || (ERROR_SUCCESS == status                 
        && 0 != wcscmp(wszNewData, wszOldData)))    
{
        dwSize = sizeof(wchar_t) * (wcslen(wszNewData) + 1);
        RegSetValueEx(hKey, wszValue, 0, REG_SZ, (const BYTE *)wszNewData, dwSize);
    }
//关闭注册表
    RegCloseKey(hKey);
}

          代码看完之后,我们来做一下CCApplication类的总结:

          在CCApplication的构造函数中可以看到这里定义了一个静态指针sm_pShareApplication;它在CCApplication实例化时指定为实例化单件对象的指针。在sharedApplication函数中返回实例化的单件对象引用。重点函数是run函数。在run函数开始调用了initInstance函数进行程序的初始化,调用返回为true后再调用applicationDidFinishLaunching函数完成一些逻辑的初始化工作,完成初始化后会进入WINDOWS消息循环,并通过高精度定时器进行FPS判断什么时候调用CCDirector::sharedDirector()->mainLoop()。直待收到窗口关闭的消息退出消息循环并返回。

好了,我们理解了CCApplication之后,我们再来看一下它的派生类AppDelegate,了解一下它都重载了哪些函数。

读前小提示:  CCEGLView代表OpenGL显示窗口。封装了使用OpengGL做为显示底层API的一个基本的WINDOWS窗体的创建与控制。

读前小提示:  CCDirector类中有一个函数enableRetinaDisplay函数。这个函数主要是影响到IOS平台上支持高清显示的设备如iphone4iphone4s等。如果设置enableRetinaDisplay(false), 则在iphone4平台上运行的结果是游戏的图片分辨率降低为原来的一半,因为宽高都被拉伸了一倍。如果设置enableRetinaDisplay(true), 则在iphone4平台上运行的结果是游戏的图片分辨率正常,但是放置的位置的水平方向和垂直方向都拉伸了两倍。要记住在cocos2d里面设置精灵坐标时,使用的是点,而不是像素,在普通的iphone上,一个点就等于一个像素,而在高清显示设备中,默认一个点等于二个像素。在IOS SDK 4.0及以后的SDK中支持不同的分辨率。并提供了相应的函数对逻辑点与像素的对应比例值做设置。

重点关注函数:initInstance()。

#ifndef  _APP_DELEGATE_H_
#define  _APP_DELEGATE_H_
#include "CCApplication.h"
class  AppDelegate : private cocos2d::CCApplication
{
public:
//构造函数
AppDelegate();
//析构函数
virtual ~AppDelegate();
    //重载初始化函数
    virtual bool initInstance();
    //重载应用程序启动后调用的处理函数
    virtual bool applicationDidFinishLaunching();
    //重载应用程序转入后台时调用的函数
    virtual void applicationDidEnterBackground();
    //重载应用程序恢复前台时调用的函数
    virtual void applicationWillEnterForeground();
};
#endif // _APP_DELEGATE_H_

再来看cpp文件

#include "AppDelegate.h"
//加入cocos2d头文件
#include "cocos2d.h"
#include "HelloWorldScene.h"
//加入OpenGL窗口类
#include "CCEGLView.h"
//使用cocos2d命名空间
USING_NS_CC;
//构造函数
AppDelegate::AppDelegate() {
}
//析构函数
AppDelegate::~AppDelegate() {
}
//初始化函数,解答伏笔1
bool AppDelegate::initInstance() 
{
//定义一个bool返回值用于判断是否正常初始化
bool bRet = false;
do {
//通过对宏定义的匹配来判断当前编译的代码的目标平台是什么,在这里可以知道Cocos2d-x的跨平台都支持哪些平台。我们的教程只针对WIN32平台部分做讲解,其它平台大家可以自行参考WIN32平台部分进行学习。
//第一种平台类型,WIN32系统
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
// 创建并初始化OpenGL窗口管理实例对象,注意这里使用的是new来动态实例化的。当程序退出时会通过显示设备的release函数进行对窗口的释放。【伏笔3后面会有讲解】。
CCEGLView * pMainWnd = new CCEGLView();
//CC_BREAK_IF宏的意思是如果括号中的语句为否则中断循环。配合do_while流程使用。
//可以看到这里的意思是,如果pMainWnd实例化失败或者pMainWnd创建窗口失败则中断循环。在这里硬性的设定了窗口的标题和窗口的宽度高度。
CC_BREAK_IF(! pMainWnd
|| ! pMainWnd->Create(TEXT("cocos2d: Hello World"), 480, 320));
#endif  // CC_PLATFORM_WIN32
   //第二种平台类型,IOS类型
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
#endif  // CC_PLATFORM_IOS
//第三种平台类型,ANDORID类型
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#endif  // CC_PLATFORM_ANDROID
    //第四种平台,WOPHONE平台
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WOPHONE)
#endif  // CC_PLATFORM_WOPHONE
//第五种平台,MARMALADE
#if (CC_TARGET_PLATFORM == CC_PLATFORM_MARMALADE)
#endif
//第六种平台,LINUX
#if (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
#endif  // CC_PLATFORM_LINUX
//第七种平台,BADA
#if (CC_TARGET_PLATFORM == CC_PLATFORM_BADA)
#endif  // CC_PLATFORM_BADA
//第八种平台,QNX
#if (CC_TARGET_PLATFORM == CC_PLATFORM_QNX)
#endif // CC_PLATFORM_QNX
//如果没有被中断,则成功完成初始化。
bRet = true;
//退出循环
} while (0);
return bRet;
}
    //重载应用程序启动后调用的处理函数
bool AppDelegate::applicationDidFinishLaunching() {
//通过CCDirector的静态函数sharedDirector来获取单件显示设备管理器指针
CCDirector *pDirector = CCDirector::sharedDirector();
//通过CCEGLView的静态函数sharedOpenGLView来获取单件管理实例对象并将其地址通过CCDirector的成员函数setOpenGLView传递给显示设备管理器。
pDirector->setOpenGLView(&CCEGLView::sharedOpenGLView());
//打开使用高清模式,这里给屏蔽了,如未来有需要可以打开。但是在设计程序时需要考虑到打开后对于位置的影响并提前制定相应的设计方案。
//pDirector->enableRetinaDisplay(true);
// 打开显示FPS
pDirector->setDisplayFPS(true);
// 设置当前屏幕的摆放方向,这里给屏蔽了。
// pDirector->setDeviceOrientation(kCCDeviceOrientationLandscapeLeft);
// 设置FPS的帧间隔时间差为60分之一秒,从而期望FPS为60帧。
pDirector->setAnimationInterval(1.0 / 60);
//通过HelloWorld的静态函数scene()创建返回一个场景实例
CCScene *pScene = HelloWorld::scene();
//运行这个场景
pDirector->runWithScene(pScene);
return true;
}
//重载应用程序转入后台时调用的函数,如电话打进来
 void AppDelegate::applicationDidEnterBackground() {
//暂停显示设备的渲染处理
CCDirector::sharedDirector()->pause();
// 如果使用了声音引擎,这里进行暂停设置。否则会影响到通话,因为暂时没用到,所以屏蔽
// SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();
}
//重载应用程序恢复前台时调用的函数
void AppDelegate::applicationWillEnterForeground() {
//恢复显示设备的渲染处理
CCDirector::sharedDirector()->resume();
//如果使用了声音引擎,这里进行恢复设置
// SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();
}

InitInstance函数中,我们看到Cocos2d-xWindows窗口的创建与控制都封装到了CCEGLView类中。它已经暴露出一个Create函数,做为一个求知者,我们应该迫不及待的想要看到它在哪里创建窗口。继续追进去吧!GoGoGo!

我们进入了CCEGLView_win32.h,通过文件名可以知道CCEGLView类应该有多个平台的版本。我们暂不理它。看一下Create函数。

//

bool CCEGLView::Create(LPCTSTR pTitle, int w, int h)
{
//定义一个bool型返回值
bool bRet = false;
do 
{
//检测窗口句柄是否已经存在。确保只创建一个窗口
CC_BREAK_IF(m_hWnd);
//进入到这里,我们应该很高兴了。没错,终于找到了创建窗口的代码。
HINSTANCE hInstance = GetModuleHandle( NULL );
WNDCLASS  wc;// 窗口类
// 设置相关参数
wc.style          = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;  
wc.lpfnWndProc    = _WindowProc;// 本窗口使用的WINDOWS消息处理函数
wc.cbClsExtra     = 0;                              
wc.cbWndExtra     = 0;
wc.hInstance      = hInstance;
wc.hIcon          = LoadIcon( NULL, IDI_WINLOGO );
wc.hCursor        = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground  = NULL;                           
wc.lpszMenuName   = NULL;                           
wc.lpszClassName  = kWindowClassName;               
//注册窗口类,如果失败则判断失败原因是否是此类别已存在(错误码1410),如果是因为此类存在而导致失败,则仍然可以继续。
CC_BREAK_IF(! RegisterClass(&wc) && 1410 != GetLastError());
// 取得窗口的窗体矩形
RECT rcDesktop;
GetWindowRect(GetDesktopWindow(), &rcDesktop);
// 调用创建口函数
m_hWnd = CreateWindowEx(
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,
kWindowClassName,// 之前注册的窗口类
pTitle,// 窗口标题
WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX,// 窗体样式
0, 0,              // 窗体位置
0,                                                  // 窗体宽度
0,                                                  // 窗体高度
NULL,// 无父窗口
NULL,// 无菜单
hInstance,// 程序句柄
NULL );
//判断窗口是否创建成功
CC_BREAK_IF(! m_hWnd);
   //取得显示设备的摆放方向
        m_eInitOrientation = CCDirector::sharedDirector()->getDeviceOrientation();
   //通过对摆放方向的判断得出水平还是垂直
        m_bOrientationInitVertical = (CCDeviceOrientationPortrait == m_eInitOrientation
            || kCCDeviceOrientationPortraitUpsideDown == m_eInitOrientation) ? true : false;
        m_tSizeInPoints.cx = w;
        m_tSizeInPoints.cy = h;
   //设置窗口大小
        resize(w, h);
// 使用此窗口进行OpenGL的设置
m_pEGL = CCEGL::create(this);
// 如果OpenGL创建失败,销毁窗体并中断
if (! m_pEGL)
{
DestroyWindow(m_hWnd);
m_hWnd = NULL;
break;
}
//将静态指针设置为当前实例对象。
s_pMainWindow = this;
bRet = true;
} while (0);
return bRet;
}

 

       在注册类时,设定了窗口的消息处理回调函数为_WinDowProc,我们继续追入看一下程序退出时在哪里释放了Opengl窗口实例对象。呵呵,还记得前面我们埋下的伏笔3,它是用new创建出来的。而new出来的内存必须释放。程序退出会销毁窗体,消息处理函数会先后收到WM_CLOSE和WM_DESTROY。我们找一下。

static LRESULT CALLBACK _WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//如果OpenGL窗体初始化完成,则由OpenGL窗体进行消息处理,否则由WINDOWS系统进行默认处理
if (s_pMainWindow && s_pMainWindow->getHWnd() == hWnd)
{
return s_pMainWindow->WindowProc(uMsg, wParam, lParam);
}
else
{
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}

继续。

LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
switch (message)
{…//其它消息暂不理,可自行分析
case WM_PAINT:
//这里是刷新屏幕,因为使用了OpenGL来进行绘制,所以这里不作任何处理。
BeginPaint(m_hWnd, &ps);
EndPaint(m_hWnd, &ps);
break;
case WM_CLOSE:
//调用单件显示设备管理器的end函数关闭窗口
CCDirector::sharedDirector()->end();
break;
case WM_DESTROY:
//向消息循环发送WM_QUIT消息退出程序
PostQuitMessage(0);
break;
default:
return DefWindowProc(m_hWnd, message, wParam, lParam);
}
return 0;
}

       进入显示设备管理器CCDirectorend函数。

void CCDirector::end()
{
m_bPurgeDirecotorInNextLoop = true;
}

      倒!这里只是设置成员变量m_bPurgeDirecotorInNextLoop为true,没有什么delete。怎么回事呢?

      好吧,继续分析,成员变量的名字意思是“是否在下一个循环时清除显示设备”。哦。也就是说这只是一个开关。在循环函数中判断它是否为true来清除显示设备。打开mainLoop函数。解答伏笔2

void CCDisplayLinkDirector::mainLoop(void)
{//如果清除显示设备开关为true
if (m_bPurgeDirecotorInNextLoop)
{
//清楚设备
purgeDirector();
          m_bPurgeDirecotorInNextLoop = false;
}//否则判断显示设备是否有效
else if (! m_bInvalid)
 {
//如果有效,绘制场景
 drawScene();
 
 //调用内存管理器释放其管理的内存
 CCPoolManager::getInstance()->pop();
 }
}

       马上要接晓答案了

void CCDirector::purgeDirector()
{
…//其它暂不理。有兴趣的朋友请自行分析
// 调用Opengl窗口管理实例对象的release函数进行释放。
m_pobOpenGLView->release();
m_pobOpenGLView = NULL;
}

       进入OpenGL窗口管理类的release函数

void CCEGLView::release()
{//销毁窗体
if (m_hWnd)
{
DestroyWindow(m_hWnd);
m_hWnd = NULL;
}
s_pMainWindow = NULL;
//注销所使用的窗口类
UnregisterClass(kWindowClassName, GetModuleHandle(NULL));
    //释放使用到的指针
    CC_SAFE_DELETE(m_pSet);
    CC_SAFE_DELETE(m_pTouch);
    CC_SAFE_DELETE(m_pDelegate);
CC_SAFE_DELETE(m_pEGL);
//关键点:在最后释放了自已在AppDelegate::initInstance()中通过new创建对象而占用的内存。
    delete this;
}

       现在我们了解了Cocos2d-x是如何将WINDOWS程序基本框架封装在自已的类之中的。运行一下程序,可们可以看到弹出的窗口显示出HelloWorld的画面。而当初复杂的WINDOWS程序基本框架。在这里只化为简单的一句run()。非常简洁!

      这个运行中的窗口,没有最大化系统按钮,窗体大小也被固定在480×320。我们希望能够更灵活的设置窗口的标题和大小。那我们必须要做一些改动。即然已经看懂了代码,下面就亲自实习一下吧。

      打开AppDelegateinitInstance函数,增加相应的参数。

bool AppDelegate::initInstance(LPCTSTR szTitle,UINT wWidth,UINT wHeight)

      并改动下面一句

CC_BREAK_IF(! pMainWnd
//改成由函数参数来创建OpenGL窗体
|| ! pMainWnd->Create(szTitle, wWidth, wHeight));
//|| ! pMainWnd->Create(TEXT("cocos2d: Hello World"), 480, 320));

       然后我们打开CCApplication_win32.cpp,找到run函数。做同样的修改。

int CCApplication::run(LPCTSTR szTitle,UINT wWidth,UINT wHeight)

并改动下面一句

if (! initInstance(szTitle,wWidth,wHeight) || ! applicationDidFinishLaunching())
// if (! initInstance() || ! applicationDidFinishLaunching())

      之后我们将头文件所需要改动的函数声明也一一改动。保存,重新编译libcocos2d工程。再打开HelloWorld中的main.cpp修改这一句:

//我们可以在这里自已设定窗口标题和窗口大小

return cocos2d::CCApplication::sharedApplication().run(TEXT("第一个Cocos2d-x程序"),800,600);
//return cocos2d::CCApplication::sharedApplication().run();

      编译HelloWorld。运行一下。享受一下成果!

                           Cocos2d-x 的“HelloWorld” 深入分析     

                                

        对于这个框架我写的较多,因为整个Cocos2d-x的所有例子都是基于这个框架,我们必须理解清楚它是怎么一回事。之后我们将进入到引擎显示部分模块的讲解。希望大家后面继续努力!下课:

 

 

分类: 未分类 标签:

VS2005也能运行Cocos2d-x

2012年8月20日 没有评论

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier]

红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x

 

Cocos2d-x的开发包中并未给出VS2005的工程文件。不巧我的电脑中只装了VS2005。学习心切,这可怎么办啊?没关系。只要您稍花点时间,也可以让Cocos2d-xVS2005中运行起来。

首先。将cocos2d-win32.vc2008.sln复制一份重命名为cocos2d-win32.vs2005.sln.然后用记事本打开它。将“Microsoft Visual Studio Solution File, Format Version 10.00

# Visual Studio 2008”一句改为“Microsoft Visual Studio Solution File, Format Version 9.00

# Visual Studio 2005”。保存文件。

然后。到Box2D,chipmunk,cocos2dx,CocosDenshion,HelloLua,HelloWorld,lua,tests目录中的proj.win32找到相应的工程文件各复制一份重新命名。如Box2D.Win32.vcproj复制文件重命名为Box2D.win32.vs2005.vcproj。用记事本打开。将

<VisualStudioProject

ProjectType="Visual C++"

Version="9.00"     

修改为

<VisualStudioProject

ProjectType="Visual C++"

Version="8.00"     

保存文件

最后。在cocos2d-win.vs2005.sln中找到相应的工程信息段。将对应的工程文件路径改为新名字。如

Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libBox2D", "Box2D/proj.win32/Box2D.win32.vcproj", "{929480E7-23C0-4DF6-8456-096D71547116}"

改为

Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libBox2D", "Box2D/proj.win32/Box2D.win32.vc2005.vcproj", "{929480E7-23C0-4DF6-8456-096D71547116}"

好了,用VS2005打开cocos2d-win.vs2005.sln然后编辑运行即可。你可能遇到下面两种错误:

1“LSTATUS”未声明的标识符,将其改为LONG .

2. libpng.lib调试信息损坏请重新编译。不要以为真的是库损坏了。你需要到<https://connect.microsoft.com/VisualStudio/Downloads/DownloadDetails.aspx?DownloadID=18623>

下载相应的VS补丁包。下载后运行完成即可修复VS的这个BUG。之后就可以在VS2005中成功编译完所有的工程。我的机器是32位的WindowsXP,下载VS80sp1-KB949009-X86-INTL.exe 安装就可以了。

3. 如果打开工程后有提示需要安装“ Microsoft .NET Framework V2.0.50727 Redistributable Package”或其它版本的.net补丁包,可上百度查询下载完装一下。

分类: 未分类 标签:

开始学习Cocos2d-x进行2D的跨平台游戏开发!

2012年8月20日 没有评论

            时光变幻。长期以来一直在端游做开发。抬头一看,PC端游,WEBGAME,移动平台已经三分天下。看PC端游无论是在市场前景和投资额度上都有减弱迹象。

未雨绸缪一下。决定开始学习一下跨平台的开发。聊以此章为记。

           本人早年也曾经学过J2ME和EVC,在JAVA手机游戏和WindowsMobile上都有尝试过开发小游戏。对于Android近段时间也有一些涉猎。也有小作品出阁。但主要精力还是一直放在客户端网游上。加上结婚生女。奈何没有太多时光学习新的技术。今朝在网上闲逛。碰到HiMi的博客,得知小小年纪已经在移动开发领域颇有建树。羡慕佩服至极。故亦激励自已希望重拾自我,奋发图强。通过了解得知有Cocos2d-x可以在Win下进行跨平台游戏开发。兴奋异常。击掌为乐。

          思考良久,决定趁光阴未老,假时以学习之得将出一书,为Cocos2d-x的普及献薄力一分。

          本博今后亦将陆续推出一些Cocos2d-x的技术文章。望诸公关注。

          

         

分类: 未分类 标签:

Unity3D+免费工具开发2D游戏之四:创建计分界面

2012年8月15日 没有评论

本篇文章中,我们将创建一个可以被捡起的物体,并完成计分系统,然后再添加一个文本界面,用来显示分数、生命和关卡。

 

点击这里查看本节完成后的效果:点我!

 

添加计分和拾取物脚本

 

到目前为止,咱们的游戏还不错啊——主角可以移动、爬梯子以及沿着绳子攀岩。现在我们来添加拾取物“金库”和一个界面,这个界面记录着分数、主角生命和当前的关卡级别。

 

  1. Download
    scoring and pickup scripts
    ,下载后解压。
  2.  Scoring.cs 和 Pickup.cs 拷贝到项目的Assets/Scripts目录下。

 

对脚本进行修改

 

  • 打开 xa.cs,取消注释掉下面一行:
public static Scoring sc;

 

  • 在Start函数里,取消注释下面一行:
sc = (Scoring)(this.gameObject.GetComponent("Scoring"));

 

  • 打开Player.cs,找到OnTriggerEnter function函数(256行),然后取消注释下面一段代码:
if (other.gameObject.CompareTag("Pickup"))
{
    if (other.GetComponent<Pickup>())
    {
        other.GetComponent<Pickup>().PickMeUp();
        xa.sc.Pickup();
    }
}

制作拾取物精灵

  • 将Orthello –> Objects –> Sprites下的Sprite拖放到Hierarchy 或Scene中,重命名为“pickup”。
  • OT –> Containers下的level对象拖放到Sprite Container中。
  •  “Frame
    Index
    改为16.
  • 将Scale里的x、y改为1。

 

调整下碰撞

  • 这个精灵是个正方形,但是我们只希望只有当玩家碰到它的白色部分才能捡起它。
  • 勾选Collidable ,给精灵添加碰撞检测。
  • 点击Physics 旁的下拉菜单,选择Custom。
  • Box Collider下面,将Center
    Y改为
    -0.15Size
    X
    改为0.8Size
    Y
     改为0.5.
    保留
    Size Z 为 0.4。

设置标签

  • 我们需要标记拾取物,这样当玩家碰到它时才会触发它碰撞。
  • 通过 Edit
    –> Project Settings –> Tags
    创建一个新的Tag
  • 将新Tag命名为Pickup” 
  • 点选pickup对象,在Tags旁的下拉菜单里选择“Pickup”


添加脚本,并将其改为Prefab

  • Pickup.cs脚本拖放到pickup对象上
  • 将pickup对象拖放到Prefabs文件夹中

根据前面几节学的知识,将pickup对象放到砖地上吧。如果你根据我的步骤做的,你应该看到下图结果:

Unity3D+免费工具开发2D游戏之四:创建计分界面

 

添加界面上的文字

待续。。。

 

 

 

 

 

 

 

 

 

分类: unity3d 标签:

Unity3D游戏制作(一)——3D横版场景的角色移动控制

2012年8月2日 没有评论

本系列文章由 Amazonzx 编写,欢迎转载,转载请注明出处。

http://blog.csdn.net/amazonzx/article/details/7824112

 

开博写Unity的东西也写了好多,但大部分都是翻译,原创很少,接下来的一段日子,我会多写一些原创文章,介绍一些在项目中积累的简单实用的技术。

 

一、导入场景,并在场景中加入TouchPlane

TouchPlane为鼠标屏幕时的Raycast平面,如下图场景中的绿线部分。由于是横版场景,地面一般是平坦的,所以可以选择进行一个平面来作为计算鼠标投射交点。

Unity3D游戏制作(一)——3D横版场景的角色移动控制

之所以是绿线,是因为我disable该平面的MeshRender,该平面的Inspector视图如下:

Unity3D游戏制作(一)——3D横版场景的角色移动控制

值得注意的是:
1、 该平面使用Box Collider,而不用Mesh Collider,这样做的好处是可以减少碰撞的计算量;
2、 Tag设定为“Plane”,这是为了鼠标点击时的Raycast选取;
3、 Layer设定为“TouchPlane”,这样做也是为了以后进行Raycast鼠标选取操作。

二、设定角色Component

Unity3D游戏制作(一)——3D横版场景的角色移动控制

角色Inspector视图如下图所示:

Unity3D游戏制作(一)——3D横版场景的角色移动控制

一共四个Component:Animation、Rigidbody、Capsule Collider以及Move Controller。

Animation组件主要是角色的动作动画;

Rigidbody组件是为了角色的移动,这个我在后面会解释,这里还有一点需要注意就是不使用“Use Gravity”,这样做一是因为角色只在地面上跑(如果你的游戏需要角色有跳跃功能,那么应该使用“Use Gravity”,二是可以在不影响效果的同时,减少模型的物理计算);

Capsule Collider是碰撞器,与该文章所介绍移动内容没有关系;

Move Controller是自定义的角色移动控制组件,其中MoveController.cs为其对应脚本。

三、如何移动角色
流程可设定如下:
1、 鼠标点击地面,通过屏幕位置来计算出其所在三维空间中角色移动的目的位置。
2、 将角色从当前位置移动到鼠标点击位置
这样,我们就根据上述两个步骤来完成人物的移动操作。
(1) 鼠标拾取操作

void Move()
{
	if(Input.GetMouseButtonDown(0))
	{
		// m_layerMask是指TouchPlane的layer数,这也是为什么之前在设定//TouchPlane时要设定其layer的原因,这样做是为了方便鼠标拾取
		m_layerMask = 1 << 8;
		// 根据鼠标在屏幕空间的位置计算射线
		m_ray = Camera.main.ScreenPointToRay(Input.mousePosition);
		// 进行三维场景中的射线求交
		if (Physics.Raycast(m_ ray, out m_ hitInfo, 100, m_layerMask))
		{
			// 如果拾取的tag为“Plane”的话
			if (m_hitInfo.transform.tag == "Plane")
			{
				// 将角色朝向目标点
				LookatTargetPos(m_ hitInfo.point);
			}
		}
	}
}

 

(2) 角色移动操作

void LookatTargetPos(Vector3 tarPos)
{
	// 判断当前角色是否可以移动
	if (!m_bWalk)
	  	return;
	// 记录下目标点
	m_targetPos = new Vector3(tarPos.x, tarPos.y, tarPos.z);
	// 将角色朝向目标点
	transform.LookAt(m_targetPos);
	// 改变移动State
	m_bMoving = true;
}

 

MoveController.cs中的Update函数如下:

void Update () 
{
	Move();
	// 如果可以移动的话
	if (m_bMoving)
	{
		// 改变角色的Animation
		animation.CrossFade("Run");
		// 设定rigidbody的速度,由于之前已经将角色朝向目标点,所以现在的速度朝向即为transform.forward
	    rigidbody.velocity = transform.forward * 8.0f;
	
		// 判断角色是否该停止移动
	    if (Vector3.Distance(transform.position, m_targetPos) < 0.1f)
	    {
	      	rigidbody.velocity = Vector3.zero;
	      	m_bMoving = false;
		  	animation.CrossFade("Idle");
	    }
	}
}

 

四、实现效果

通过以上设置即可控制角色在横版场景中的移动,效果图如下:

Unity3D游戏制作(一)——3D横版场景的角色移动控制

 

五、小结

1、该做法只适合平面的地表,如果是有起伏的,则需要使用Navmesh或其他trick来解决。

2、该做法并没有让角色与地面去做碰撞,因为本身是平面的,所以直接限定其y值,不让其使用重力作用,这样的好处可以避免不必要的物理计算。

3、角色的移动可以不用rigidbody来搞定,一般的做法还是通过Time.deltatime来一帧一帧来计算步长、移动物体,这里只是给出另外一种方法,原来其实是一样的。

分类: 未分类 标签: