马上加入IBC程序猿 各种源码随意下,各种教程随便看! 注册 每日签到 加入编程讨论群

C#教程 ASP.NET教程 C#视频教程程序源码享受不尽 C#技术求助 ASP.NET技术求助

【源码下载】 社群合作 申请版主 程序开发 【远程协助】 每天乐一乐 每日签到 【承接外包项目】 面试-葵花宝典下载

官方一群:

官方二群:

Cocos2d-x入门之旅[1]场景

[复制链接]
查看2308 | 回复1 | 2019-10-17 09:43:54 | 显示全部楼层 |阅读模式

在游戏开辟过程中,你大概必要一个主菜单,几个关卡和一个END的界面,怎样组织管理这些东西呢?

和其他游戏引擎类似,Cocos也使用了场景(Scene) 这个概念

试想象一部电影或是番剧,你不难发现它是被分解为差别场景或差别时间线的,这些部分就是一个又一个的场景

参考:https://www.cnblogs.com/NightFrost/p/11688854.html

场景的存储布局

为了表明场景的布局,我们先不看我们过于简单的helloworld场景,看下面这个官方文档的场景:

094354b48owaed3wq4t332.png

这是一个主菜单场景,这个场景是由许多小的对象拼接而成,全部的对象组合在一起,形成了你看到的结果

场景是被渲染器(renderer)画出来的,渲染器负责渲染精灵和其它的对象进入屏幕,那渲染器怎么知道什么东西要渲染在后,什么东西要渲染在前呢?

答案是通过场景图(Scene Graph)实现

场景图(Scene Graph)

Cocos2d-x使用场景图(Scene Graph)这一数据布局来安场面景内渲染的对象,场景内全部的节点(Node)都包罗在一个树(tree)上:

094355olgjfiixjh6gfty2.png

Cocos2d-x使用 中序遍历,先遍历左子树,然后根节点,最后是右子树

中序遍历下图的节点,能得到 A, B, C, D, E, F, G, H, I 这样的序列

094355xsppt6gqmkcz08cm.png

如今我们再看这个游戏场景:

094355tw4k4kkick0kfnxl.png

分解这场景为5个部分

094356zymhyg9mmopg0nfh.png

抽象成数据布局就是:

094356mvrqqy0v4rvqzqrx.png

z-order

树上的每个元素都会存储一个z-order,z-order为负的元素,z-order为负的节点会被放置在左子树,非负的节点会被放在右子树,实际开辟的过程中,你可以按照恣意顺序添加对象,他们会按照你指定的 z-order 主动排序

在 Cocos2d-x 中,通过 SceneaddChild() 方法构建场景图

  1. <code>// Adds a child with the z-order of -2, that means
  2. // it goes to the "left" side of the tree (because it is negative)
  3. scene->addChild(title_node, -2);
  4. // When you don&#39;t specify the z-order, it will use 0
  5. scene->addChild(label_node);
  6. // Adds a child with the z-order of 1, that means
  7. // it goes to the "right" side of the tree (because it is positive)
  8. scene->addChild(sprite_node, 1);</code>
复制代码

渲染时 z-order 值大的节点对象后绘制值小的节点对象先绘制,假如两个节点对象的绘制范围有重叠,z-order 值大的大概会覆盖 z-order 值小的,这才实现了我们的需求

HelloWorld场景

如今我们回看我们运行出来的HelloWorld场景,而且详细到代码操作

场景中有一个我们本身的图片,一个关闭按钮,一个HelloWorld的字样,这些东西都是在HelloWorld::init()中天生的

场景初始化

我们向HelloWorld场景添加东西之前,必要先调用基类Scene类的初始化函数,然后得到visibleSizeorigin备用

  1. <code>bool HelloWorld::init()
  2. {
  3. //////////////////////////////
  4. // 1. super init first
  5. if ( !Scene::init() )
  6. {
  7. return false;
  8. }
  9. auto visibleSize = Director::getInstance()->getVisibleSize();
  10. Vec2 origin = Director::getInstance()->getVisibleOrigin();
  11. ...
  12. }</code>
复制代码

关闭按钮的天生

相干代码如下

  1. <code>bool HelloWorld::init()
  2. {
  3. ...
  4. /////////////////////////////
  5. // 2. add a menu item with "X" image, which is clicked to quit the program
  6. // you may modify it.
  7. // add a "close" icon to exit the progress. it&#39;s an autorelease object
  8. auto closeItem = MenuItemImage::create(
  9. "CloseNormal.png",
  10. "CloseSelected.png",
  11. CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
  12. if (closeItem == nullptr ||
  13. closeItem->getContentSize().width <= 0 ||
  14. closeItem->getContentSize().height <= 0)
  15. {
  16. problemLoading("&#39;CloseNormal.png&#39; and &#39;CloseSelected.png&#39;");
  17. }
  18. else
  19. {
  20. float x = origin.x + visibleSize.width - closeItem->getContentSize().width/2;
  21. float y = origin.y + closeItem->getContentSize().height/2;
  22. closeItem->setPosition(Vec2(x,y));
  23. }
  24. // create menu, it&#39;s an autorelease object
  25. auto menu = Menu::create(closeItem, NULL);
  26. menu->setPosition(Vec2::ZERO);
  27. this->addChild(menu, 1);
  28. ...
  29. }</code>
复制代码

cocos里许多对象在天生的时间都会使用create这个静态工厂方法,我们创建图片精灵的时间就用到了auto mySprite = Sprite::create("xxxxxx.png"),HelloWorld这个场景也不例外

MenuItemImage的create方法传入默认状态的close按钮的图片点击状态下的close按钮的图片以及一个回调,回调指的是程序对按钮被按下这个变乱做出的相应,看不懂没关系,照着写就好

  1. <code>auto closeItem = MenuItemImage::create(
  2. "CloseNormal.png",
  3. "CloseSelected.png",
  4. CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));</code>
复制代码

然后就是计算出x和y的值,也就是右下角的按钮的坐标,getContentSize()得到对象的尺寸,最后使用setPosition设置按钮的坐标

  1. <code>if (closeItem == nullptr ||
  2. closeItem->getContentSize().width <= 0 ||
  3. closeItem->getContentSize().height <= 0)
  4. {
  5. problemLoading("&#39;CloseNormal.png&#39; and &#39;CloseSelected.png&#39;");
  6. }
  7. else
  8. {
  9. float x = origin.x + visibleSize.width - closeItem->getContentSize().width/2;
  10. float y = origin.y + closeItem->getContentSize().height/2;
  11. closeItem->setPosition(Vec2(x,y));
  12. }</code>
复制代码

但是按钮是不可以直接添加到场景中的,按钮必要依赖菜单,也就是Menu对象

我们创建一个包罗了closeItem的菜单,并设置坐标为(0,0),最后才气使用addChild将菜单添加到场景中

  1. <code>// create menu, it&#39;s an autorelease object
  2. auto menu = Menu::create(closeItem, NULL);
  3. menu->setPosition(Vec2::ZERO);
  4. this->addChild(menu, 1); </code>
复制代码

字体的天生

  1. <code>bool HelloWorld::init()
  2. {
  3. ...
  4. auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
  5. //Label::createWithTTF(表现的字符串,字体,字体大小);
  6. if (label == nullptr)
  7. {
  8. problemLoading("&#39;fonts/Marker Felt.ttf&#39;");
  9. }
  10. else
  11. {
  12. // position the label on the center of the screen
  13. label->setPosition(Vec2(origin.x + visibleSize.width/2,
  14. origin.y + visibleSize.height - label->getContentSize().height));
  15. // add the label as a child to this layer
  16. this->addChild(label, 1);
  17. }
  18. ...
  19. }</code>
复制代码

这个也很好明白,Label::createWithTTF返回一个Label对象的指针,表现的字符串字体字体大小作为函数的参数,也是使用addChild添加到场景中,这里的1比0高一层,我们试着把文本的坐标设置到场景中央,修改成如下:

  1. <code>auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
  2. label->setPosition(Vec2(origin.x + visibleSize.width/2,
  3. origin.y + visibleSize.height/2));
  4. this->addChild(label, 1);</code>
复制代码

运行

094408q44s4szh48nv08mn.png

文本是在logo上方的,验证了 z-order 值大的节点对象后绘制,值小的节点对象先绘制,先渲染的被压在后渲染的物体下面

精灵的天生

  1. <code>bool HelloWorld::init()
  2. {
  3. ...
  4. auto sprite = Sprite::create("sinnosuke.png");
  5. if (sprite == nullptr)
  6. {
  7. problemLoading("&#39;HelloWorld.png&#39;");
  8. }
  9. else
  10. {
  11. // position the sprite on the center of the screen
  12. sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
  13. // Vec2(visibleSize.width/4 + origin.x, visibleSize.height/2 + origin.y)
  14. // add the sprite as a child to this layer
  15. this->addChild(sprite, 0);
  16. }
  17. ...
  18. }</code>
复制代码

更简单了,使用一张图片天生一个精灵,同样也是加到场景中,最后要记得return true

094409f786xx0d6dxvxf10.png

深入探索HelloWorld场景

场景入口

起首,游戏场景的入口是导演类的runWithScene,打开AppDelegate.cpp,找到AppDelegate::applicationDidFinishLaunching()函数,可以看到:

  1. <code>Copybool AppDelegate::applicationDidFinishLaunching() {
  2. // initialize director
  3. auto director = Director::getInstance();
  4. ...
  5. // create a scene. it&#39;s an autorelease object
  6. auto scene = HelloWorld::createScene();
  7. // run
  8. director->runWithScene(scene);
  9. return true;
  10. }</code>
复制代码

Director类是一个单例类,使用getInstance可以得到它的实例,(单例模式包管体系中应用该模式的类一个类只有一个对象实例)我们必要Director实例来运行运行HelloWorld场景(通过runWithScene),并让HelloWorld以及HelloWorld的子节点工作

Node类

Node类是HelloWorld场景里我们使用的大部分类的基类(着实Scene类也是一个Node)

游戏世界中的对象实际上大部分都是Node,就像我们一开始提到的,Node和Node通过父子关系联系起来,形成一棵树,父节点使用addChild将子节点加到本身管理的子节点队列中,游戏运行的时间,导演Director就会遍历这些Node让他们举行工作

比如我们的HelloWorld场景:HelloWorld场景是根节点,精灵sprite,文本label,菜单menu是HelloWorld的子节点,按钮closeItem是菜单menu的子节点

Ref类

Ref类是用于引用计数的类,负责对象的引用计数,Ref类是Node类的基类,也就是说全部的Node都是使用cocos2dx的引用计数内存管理体系举行内存管理的,这也是为什么我们天生对象不是用new和delete,而是用create天生对象的原因

简单来说,引用计数法的理论是,当对象被引用的时间,对象的引用计数会+1,取消引用的时间就-1,当计数为0的时间就将对象销毁,感爱好可以相识一下智能指针RAII

create

这个函数我们可以以为它是一个工厂,这个工厂把我们天生对象之前必要做的工作先做好了,在文章到达最开头有这样一段代码

  1. <code>Scene* HelloWorld::createScene()
  2. {
  3. return HelloWorld::create();
  4. }</code>
复制代码

然后HelloWorldScene.h是这样的

  1. <code>#ifndef __HELLOWORLD_SCENE_H__
  2. #define __HELLOWORLD_SCENE_H__
  3. #include "cocos2d.h"
  4. class HelloWorld : public cocos2d::Scene
  5. {
  6. public:
  7. static cocos2d::Scene* createScene();
  8. virtual bool init();
  9. void menuCloseCallback(cocos2d::Ref* pSender);
  10. CREATE_FUNC(HelloWorld);
  11. };
  12. #endif</code>
复制代码

为什么没有看到create函数,我们看CREATE_FUNC

  1. <code>#define CREATE_FUNC(__TYPE__) \
  2. static __TYPE__* create() \
  3. { \
  4. __TYPE__ *pRet = new(std::nothrow) __TYPE__(); \
  5. if (pRet && pRet->init()) \
  6. { \
  7. pRet->autorelease(); \
  8. return pRet; \
  9. } \
  10. else \
  11. { \
  12. delete pRet; \
  13. pRet = nullptr; \
  14. return nullptr; \
  15. } \
  16. }</code>
复制代码

可以看出来,CREATE_FUNC是一个可以让你偷懒不消手动编写create函数的宏

当然有的类必要客制化create,比如说Sprite的create

  1. <code>CopySprite* Sprite::create()
  2. {
  3. Sprite *sprite = new (std::nothrow) Sprite();
  4. if (sprite && sprite->init())
  5. {
  6. sprite->autorelease();
  7. return sprite;
  8. }
  9. CC_SAFE_DELETE(sprite);
  10. return nullptr;
  11. }</code>
复制代码

create里举行了什么操作呢?

  1. 使用new天生对象
  2. 使用init初始化对象
  3. 使用autorelease将这个Ref类交给引用计数体系管理内存

看到这个init我们是不是想到了什么,HelloWorld场景的布局就是在init中实现的,而init由create调用,也就是说,在HelloWorld举行create的时间就已经将文本,按钮,精灵等物件创建并参加到场景中,而这些物件也是通过create创建的,也就是说,场景创建的时间会调用全部物件的init

autorelease是Ref类的方法,检察一下它的定义

  1. <code>CopyRef* Ref::autorelease()
  2. {
  3. PoolManager::getInstance()->getCurrentPool()->addObject(this);
  4. return this;
  5. }</code>
复制代码

又看到了getInstance,分析PoolManager也是一个单例类,这段代码的意思很明显,将Ref参加到当前内存池中管理

我们在后续的开辟中常常必要客制化create,只要我们的create能满意上面三个功能即可







来源:https://www.cnblogs.com/zhxmdefj/p/11689693.html
C#论坛 www.ibcibc.com IBC编程社区
C#
C#论坛
IBC编程社区
*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则