cocos node sceen

本节的学习目标:

(1)了解结点系统,学会自行构建结点系统。

(2)了结场景,层,精灵的组织关系与各自功能



2.1 结点系统原理入门

2.1.1 结点启蒙:

在介绍Cocos2d-x的结点系统之前,我们需要首先做一些启蒙,什么是树?


定义:

一棵树(tree)是由n(n>0)个元素组成的有限集合,其中:

(1)每个元素称为结点(node);

(2)有一个特定的结点,称为根结点或根(root);

(3)除根结点外,其余结点被分成m(m>=0)个互不相交的有限集合,而每个子集又都是一棵树(称为原树的子树)

如图A:



对于树结构有几个概念要记一下:

:树的度——也即是宽度,简单地说,就是结点的分支数。以组成该树各结点中最大的度作为该树的度,如上图的树,其度为3;树中度为零的结点称为叶结点或终端结点。树中度不为零的结点称为分枝结点或非终端结点。除根结点外的分枝结点统称为内部结点。

深度:树的深度——组成该树各结点的最大层次,如上图,其深度为3;

层次:根结点的层次为1,其他结点的层次等于它的父结点的层次数加1.

请仔细观察上图这棵树,这里A是根结点,其余结点均是属于A的不同层级的子结点。我们由此图进一步进行想像,人的身体其实也是一棵树。

如图B:



上图详细表现了人体树结构的组织结构,左边是人形的结构,右边是层级关系展开。它作为骨骼动画的基础理论被广泛的应用于各类游戏动画中。

我们看一下这张图,首先有一个根骨(脊椎),这个根骨即是树结构中的根结点。根骨下有三根子骨骼(左胯,右胯,颈背),这三根子骨骼也各自有属于自已的子骨骼树结构,同时它们由父骨骼牵引并牵引着子骨骼,与父骨骼和第一层子骨骼保持着固定的距离。

试想一下:

当我们想把这个人移动到一个位置点时,只需要把根骨移动到相应位置,即这个人的所有骨骼都会被这种牵引关系移动到这个世界位置的相对骨骼位置。但如果我们把左胯这根骨骼去掉的话,则在移动根骨后,左胯仍停留在原地,它已经不再属于当前骨骼树了,而成了一棵独立的骨骼树。

看完这张图,已经比较接近我们所要讲述的内容了,对于骨骼结构的理解将有助于我们掌握远大于骨骼动画本身的结构模式,因为由此理论基础我们将学会一切基于结点树结构的系统。

下面我们来用C++的代码构建这样一套系统。

首先,我们创建一个基类,称之为结点。

  1. //结点类
  2. classCNode
  3. {
  4. public:
  5. //构造
  6. CNode();
  7. //析构
  8. virtual~CNode();
  9. public:
  10. //更新
  11. virtualinlinevoidUpdate();
  12. //渲染
  13. virtualinlinevoidDraw();
  14. public:
  15. //设置当前结点名称
  16. voidSetName(constchar*szName);
  17. //取得当前结点名称
  18. conststring&GetName();
  19. //加入一个子结点类
  20. voidAddChild(CNode*pChildNode);
  21. //取得子结点
  22. CNode*GetFirstChild();
  23. //加入一个兄弟结点类
  24. voidAddBorther(CNode*pBortherNode);
  25. //取得兄弟结点
  26. CNode*GetFirstBorther();
  27. //删除一个结点
  28. boolDelNode(CNode*pNode);
  29. //清空所有子结点
  30. voidDelAllChild();
  31. //清空所有兄弟结点
  32. voidDelAllBorther();
  33. //查询某个子结点--纵向查询
  34. CNode*QueryChild(constchar*szName);
  35. //查询某个兄弟结点--横向查询
  36. CNode*QueryBorther(constchar*szName);
  37. //为了方便检测结点树系统的创建结果,这里增加了一个保存结点树到XML文件的函数。
  38. boolSaveNodeToXML(constchar*szXMLFile);
  39. protected:
  40. //设置父结点
  41. voidSetParent(CNode*pParentNode);
  42. //取得父结点
  43. CNode*GetParent();
  44. //保存结点树到XML文件,这个函数是只生成本结点的信息。
  45. boolSaveNodeToXML(FILE*hFile);
  46. private:
  47. //当前结点名称
  48. stringm_strNodeName;
  49. //父结点
  50. CNode*m_pParentNode;
  51. //第一个子结点
  52. CNode*m_pFirstChild;
  53. //第一个兄弟结点
  54. CNode*m_pFirstBorther;
  55. }
  56. ;

对应的实现:

  1. #include"Node.h"
  2. //构造
  3. CNode::CNode()
  4. {
  5. m_strNodeName[0]='\0';
  6. m_pParentNode=NULL;
  7. m_pFirstChild=NULL;
  8. m_pFirstBorther=NULL;
  9. }
  10. //析构
  11. CNode::~CNode()
  12. {
  13. DelAllChild();
  14. }
  15. //更新
  16. voidCNode::Update()
  17. {
  18. if(m_pFirstChild)
  19. {
  20. m_pFirstChild->Update();
  21. }
  22. //在这里增加你更新结点的处理
  23. if(m_pFirstBorther)
  24. {
  25. m_pFirstBorther->Update();
  26. }
  27. }
  28. //直接渲染
  29. voidCNode::Draw()
  30. {
  31. if(m_pFirstChild)
  32. {
  33. m_pFirstChild->Draw();
  34. }
  35. //在这里增加你渲染图形的处理
  36. if(m_pFirstBorther)
  37. {
  38. m_pFirstBorther->Draw();
  39. }
  40. }
  41. //设置当前结点名称
  42. voidCNode::SetName(constchar*szName)
  43. {
  44. m_strNodeName=szName;
  45. }
  46. //取得当前结点名称
  47. conststring&CNode::GetName()
  48. {
  49. returnm_strNodeName;
  50. }
  51. //加入一个子结点类
  52. voidCNode::AddChild(CNode*pChildNode)
  53. {
  54. if(pChildNode)
  55. {
  56. if(m_pFirstChild)
  57. {
  58. m_pFirstChild->AddBorther(pChildNode);
  59. }
  60. else
  61. {
  62. m_pFirstChild=pChildNode;
  63. m_pFirstChild->SetParent(this);
  64. }
  65. }
  66. }
  67. //取得子结点
  68. CNode*CNode::GetFirstChild()
  69. {
  70. returnm_pFirstChild;
  71. }
  72. //加入一个兄弟结点类
  73. voidCNode::AddBorther(CNode*pBortherNode)
  74. {
  75. if(pBortherNode)
  76. {
  77. if(m_pFirstBorther)
  78. {
  79. m_pFirstBorther->AddBorther(pBortherNode);
  80. }
  81. else
  82. {
  83. m_pFirstBorther=pBortherNode;
  84. m_pFirstBorther->SetParent(m_pParentNode);
  85. }
  86. }
  87. }
  88. //取得兄弟结点
  89. CNode*CNode::GetFirstBorther()
  90. {
  91. returnm_pFirstBorther;
  92. }
  93. //删除一个子结点类
  94. boolCNode::DelNode(CNode*pTheNode)
  95. {
  96. if(pTheNode)
  97. {
  98. if(m_pFirstChild)
  99. {
  100. if(m_pFirstChild==pTheNode)
  101. {
  102. m_pFirstChild=pTheNode->GetFirstBorther();
  103. deletepTheNode;
  104. returntrue;
  105. }
  106. else
  107. {
  108. if(true==m_pFirstChild->DelNode(pTheNode))returntrue;
  109. }
  110. }
  111. if(m_pFirstBorther)
  112. {
  113. if(m_pFirstBorther==pTheNode)
  114. {
  115. m_pFirstBorther=pTheNode->GetFirstBorther();
  116. deletepTheNode;
  117. returntrue;
  118. }
  119. else
  120. {
  121. if(true==m_pFirstBorther->DelNode(pTheNode))returntrue;
  122. }
  123. }
  124. }
  125. returnfalse;
  126. }
  127. //清空所有子结点
  128. voidCNode::DelAllChild()
  129. {
  130. if(m_pFirstChild)
  131. {
  132. CNode*pBorther=m_pFirstChild->GetFirstBorther();
  133. if(pBorther)
  134. {
  135. pBorther->DelAllBorther();
  136. deletepBorther;
  137. pBorther=NULL;
  138. }
  139. deletem_pFirstChild;
  140. m_pFirstChild=NULL;
  141. }
  142. }
  143. //清空所有兄弟结点
  144. voidCNode::DelAllBorther()
  145. {
  146. if(m_pFirstBorther)
  147. {
  148. m_pFirstBorther->DelAllBorther();
  149. deletem_pFirstBorther;
  150. m_pFirstBorther=NULL;
  151. }
  152. }
  153. //查询某个子结点--纵向查询
  154. CNode*CNode::QueryChild(constchar*szName)
  155. {
  156. if(szName)
  157. {
  158. if(m_pFirstChild)
  159. {
  160. //如果是当前子结点,返回子结点。
  161. if(0==strcmp(szName,m_pFirstChild->GetName().c_str()))
  162. {
  163. returnm_pFirstChild;
  164. }
  165. else
  166. {
  167. //如果不是,查询子结点的子结点。
  168. CNode*tpChildChild=m_pFirstChild->QueryChild(szName);
  169. if(tpChildChild)
  170. {
  171. returntpChildChild;
  172. }
  173. //如果还没有,查询子结点的兄弟结点。
  174. returnm_pFirstChild->QueryBorther(szName);
  175. }
  176. }
  177. }
  178. returnNULL;
  179. }
  180. //查询某个兄弟结点--横向查询
  181. CNode*CNode::QueryBorther(constchar*szName)
  182. {
  183. if(szName)
  184. {
  185. if(m_pFirstBorther)
  186. {
  187. if(0==strcmp(szName,m_pFirstBorther->GetName().c_str()))
  188. {
  189. returnm_pFirstBorther;
  190. }
  191. else
  192. {
  193. //如果不是,查询子结点的子结点。
  194. CNode*tpChildChild=m_pFirstBorther->QueryChild(szName);
  195. if(tpChildChild)
  196. {
  197. returntpChildChild;
  198. }
  199. returnm_pFirstBorther->QueryBorther(szName);
  200. }
  201. }
  202. }
  203. returnNULL;
  204. }
  205. //设置父结点
  206. voidCNode::SetParent(CNode*pParentNode)
  207. {
  208. m_pParentNode=pParentNode;
  209. }
  210. //取得父结点
  211. CNode*CNode::GetParent()
  212. {
  213. returnm_pParentNode;
  214. }
  215. //保存结点树到XML
  216. boolCNode::SaveNodeToXML(constchar*szXMLFile)
  217. {
  218. FILE*hFile=fopen(szXMLFile,"wt");
  219. if(!hFile)
  220. {
  221. returnfalse;
  222. }
  223. fprintf(hFile,TEXT("<?xmlversion=\"1.0\"encoding=\"UTF-8\"?>\n"));
  224. fprintf(hFile,TEXT("<!DOCTYPEplistPUBLIC\"-//AppleComputer//DTDPLIST1.0//EN\"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"));
  225. fprintf(hFile,TEXT("<!--HonghaierGameTutorial-->\n"));
  226. fprintf(hFile,TEXT("<plistversion=\"1.0\">\n"));
  227. fprintf(hFile,TEXT("<dict>\n"));
  228. //========================================================
  229. fprintf(hFile,TEXT("<key>NodeTree</key>"));
  230. fprintf(hFile,TEXT("<dict>"));
  231. if(false==SaveNodeToXML(hFile))
  232. {
  233. fclose(hFile);
  234. returnfalse;
  235. }
  236. fprintf(hFile,TEXT("</dict>"));
  237. //========================================================
  238. fprintf(hFile,TEXT("</dict>"));
  239. fprintf(hFile,TEXT("</plist>\n"));
  240. fclose(hFile);
  241. returntrue;
  242. }
  243. //保存结点树到XML
  244. boolCNode::SaveNodeToXML(FILE*hFile)
  245. {
  246. //========================================================
  247. //fprintf(hFile,TEXT("<key>NodeName</key>"));
  248. //fprintf(hFile,TEXT("<string>%s</string>"),m_strNodeName.c_str());
  249. fprintf(hFile,TEXT("<key>%s</key>"),m_strNodeName.c_str());
  250. //========================================================
  251. fprintf(hFile,TEXT("<dict>"));
  252. if(m_pFirstChild)
  253. {
  254. if(false==m_pFirstChild->SaveNodeToXML(hFile))
  255. {
  256. fclose(hFile);
  257. returnfalse;
  258. }
  259. }
  260. fprintf(hFile,TEXT("</dict>"));
  261. if(m_pFirstBorther)
  262. {
  263. if(false==m_pFirstBorther->SaveNodeToXML(hFile))
  264. {
  265. fclose(hFile);
  266. returnfalse;
  267. }
  268. }
  269. returntrue;
  270. }

这样,一个最基本的结点就建立起来了,我们将可以由它来建立一棵树,比如下图这样一个程序:我们有一个TreeCtrl。初始情况下只有一个Root结点,通过在树控件上右键弹出菜单中进行选项操作来增加或删除子结点和兄弟结点。当我们创建了一个结点树后可以调用SaveNodeToXML函数来讲结点树保存下来。


保存的XML文件打开后:


学到这里,您已经掌握了一个结点系统的基本设计思想,它将在日后成为一个强大的武器来帮助您在游戏开发过程中解决一些相关的设计问题。

2.1.1结点的位置:

上面的结点系统代码中,只有结点的父子关系,并不能实现父结点移动同时带动子结点移动。这又是怎么做到的呢?

这里有一个关键的核心算法:即一个结点的位置,由本结点相对于父结点位置加上父结点的世界位置来取得,而父结点又会通过父结点与其父结点(即爷爷结点)的相对位置加上其父结点(即爷爷结点)的世界位置来取得。这里有一个层层回溯的思想在里面。

我们在代码中加入一个表示空间位置的结构。

  1. //向量
  2. structstVec3
  3. {
  4. //三向值
  5. floatm_fX;
  6. floatm_fY;
  7. floatm_fZ;
  8. stVec3()
  9. {
  10. m_fX=0;
  11. m_fY=0;
  12. m_fZ=0;
  13. }
  14. //构造
  15. stVec3(floatx,floaty,floatz)
  16. {
  17. m_fX=x;
  18. m_fY=y;
  19. m_fZ=z;
  20. }
  21. //重载赋值操作符
  22. stVec3&stVec3::operator=(conststVec3&tVec3)
  23. {
  24. m_fX=tVec3.m_fX;
  25. m_fY=tVec3.m_fY;
  26. m_fZ=tVec3.m_fZ;
  27. return*this;
  28. }
  29. //重载加法操作符
  30. stVec3stVec3::operator+(conststVec3&tVec3)
  31. {
  32. stVec3tResultVec;
  33. tResultVec.m_fX=m_fX+tVec3.m_fX;
  34. tResultVec.m_fY=m_fY+tVec3.m_fY;
  35. tResultVec.m_fZ=m_fZ+tVec3.m_fZ;
  36. returntResultVec;
  37. }
  38. //重载加等操作符
  39. stVec3&stVec3::operator+=(conststVec3&tVec3)
  40. {
  41. m_fX+=tVec3.m_fX;
  42. m_fY+=tVec3.m_fY;
  43. m_fZ+=tVec3.m_fZ;
  44. return*this;
  45. }
  46. //重载减法操作符
  47. stVec3stVec3::operator-(conststVec3&tVec3)
  48. {
  49. stVec3tResultVec;
  50. tResultVec.m_fX=m_fX-tVec3.m_fX;
  51. tResultVec.m_fY=m_fY-tVec3.m_fY;
  52. tResultVec.m_fZ=m_fZ-tVec3.m_fZ;
  53. returntResultVec;
  54. }
  55. //重载减等操作符
  56. stVec3&stVec3::operator-=(conststVec3&tVec3)
  57. {
  58. m_fX-=tVec3.m_fX;
  59. m_fY-=tVec3.m_fY;
  60. m_fZ-=tVec3.m_fZ;
  61. return*this;
  62. }
  63. }
  64. ;
  65. 然后在结点中加入相应接口:
  66. public:
  67. //设置当前结点相对于父结点位置
  68. voidSetPos(floatx,floatz);
  69. voidSetPos_X(floatx);
  70. voidSetPos_Y(floaty);
  71. voidSetPos_Z(floatz);
  72. //取得当前结点相对于父结点位置
  73. floatGetPos_X();
  74. floatGetPos_Y();
  75. floatGetPos_Z();
  76. //取得当前结点的世界坐标位置
  77. stVec3GetWorldPos();
  78. private:
  79. //当前结点相对于父结点的位置
  80. stVec3m_sPos;
  81. 对应的实现:
  82. //设置当前结点相对于父结点位置
  83. voidCNode::SetPos(floatx,floatz)
  84. {
  85. m_sPos.m_fX=x;
  86. m_sPos.m_fY=y;
  87. m_sPos.m_fZ=z;
  88. }
  89. voidCNode::SetPos_X(floatx)
  90. {
  91. m_sPos.m_fX=x;
  92. }
  93. voidCNode::SetPos_Y(floaty)
  94. {
  95. m_sPos.m_fY=y;
  96. }
  97. voidCNode::SetPos_Z(floatz)
  98. {
  99. m_sPos.m_fZ=z;
  100. }
  101. //取得当前结点相对于父结点位置
  102. floatCNode::GetPos_X()
  103. {
  104. returnm_sPos.m_fX;
  105. }
  106. floatCNode::GetPos_Y()
  107. {
  108. returnm_sPos.m_fY;
  109. }
  110. floatCNode::GetPos_Z()
  111. {
  112. returnm_sPos.m_fZ;
  113. }
  114. //取得当前结点的世界坐标位置
  115. stVec3CNode::GetWorldPos()
  116. {
  117. stVec3tResultPos=m_sPos;
  118. //使用回溯法取得最终的世界位置,这一步是结点系统中父结点固定子结点的关健。
  119. if(m_pParentNode)
  120. {
  121. tResultPos+=m_pParentNode->GetWorldPos();
  122. }
  123. returntResultPos;
  124. }

经过这些代码的建立,我们就可以取得一个受父结点位置固定的子结点的世界位置了。同样,缩放和旋转的关系也可以由此建立,在此就不一一赘述了,有兴趣的同学可以在本节作用中完成它。

2.2 精灵,层,场景

2.2.1魂斗罗的场景:

在Cocos2d-x中,大量的物体都是基于结点系统的,这些类均是由最基本的结点类CCNode来派生的。其中最为重要的是精灵-CCSprite,层-CCLayer,场景- CCScene。

一个游戏的一个关卡,可以想象为一棵树,其中场景是树干,层是树枝,精灵是树叶。一棵树只有一个树干,树干上有多个树枝,树枝上又有多个树叶。从功能性上来讲,树干的作用是管理树枝,树枝的作用是固定其上长出的树叶,树叶的作用是吸收阳光…NO,不是这个意思,树叶的作用是表现力,是观赏,是用来看的。表现在Cocos2d-x的游戏中,场景用来管理所有的层,而层则是用来区分具有不同排序分类的精灵集合,并能响应触屏事件,而精灵就是显示图片和动画的。当游戏要切换场景时,就像是换一棵树。作为一个游戏设计师,要能够很好的设计游戏的这些树。当然,我们要很清楚的知道如何来种下一棵树,这很重要!

首先,我们先要确定游戏都需要哪些场景。作为树的根结点,它构成了游戏的骨架。比如我们小时候玩的FC游戏-《魂斗罗》。

我们可以将开始界面和后面每一个关卡都当作是一个场景。那简单来说这个游戏是由两类场景构成的。第一类场景就是开始界面,如下图:


这个开始界面做为一个场景是简单了点,但很清晰。游戏开始时首先要运行的场景就是它。我们以三级树形结点来表示这个场景。




在这个三级树形结点图示中,“开始界面”即为场景,“界面层”即为层,再下面的四个结点可以算为界面层下的精灵,当然,菜单其实也可以分为几个精灵构成。

第二类场景就是关卡了。如图:



这是熟悉的第一关,我们仍以三级树形结点来表示这个场景。


在这里,“第一关”即为场景,为了区分具有不同排序分类的精灵集合。我将游戏中的层由远及近观看,由先及后绘制,划分为“远景层”,“近景层”,“人物层”,“效果层”,“界面层”等五个层,再将各种精灵分布到这些层中。

继续这样子分析,我们可以得出所有的关卡树:



在这里“Root”代表了游戏程序。它共种有十棵树。分别为“开始界面”,“第一关”…“通关界面”,每完成一个关卡,就将进行场景的切换,也就是显示一棵新树。

到这里,精灵,层与场景的结点关系原理已经讲解完成。我们现在来看一下Cocos2d-x中是如何具体实现和应用的。

以开始界面为例,咱们接着上一节中所讲的节点类来进行扩展,为了更好的讲述理论,这部分内容完全不涉及任何渲染引擎的使用,我们只使用VS创建一个简单的WIN32窗口程序,并使用GDI来进行绘制。

我们将创建的工程命名为ShowNodeTree,编译运行只有一个空白窗口,它工作的很好。OK,现在我们创建一个工程筛选目录NodoTree,并将之前创建的Node放入其中,并依次创建好Scene,Layer,Spriet及Director等类。



顾名思义,上面这些文件分别为:

Director.h/cpp:win32绘制管理类CDirector,绘图用。

Node.h/cpp:结点基类CNode,用于构建结点树。

Layer.h/cpp: 层类CLayer。

Scene.h/cpp:场景类CScene。

Sprite.h/cpp:精灵类CSprite。

我们来看一下具体实现:

首先是win32绘制管理类CDirector:

Director.h:

  1. #pragmaonce
  2. #include<windows.h>
  3. //==================================================================
  4. //File:Director.h
  5. //Desc:显示设备类,用于绘制
  6. //==================================================================
  7. classCDirector
  8. {
  9. public:
  10. ~CDirector();
  11. public:
  12. //获取单件实例指针
  13. staticCDirector*GetInstance();
  14. //设置HDC
  15. voidInit(HWNDhWnd);
  16. //绘制矩形
  17. voidFillRect(intx,inty,intwidth,intheight,COLORREFrgb);
  18. //绘制图像
  19. voidDrawBitMap(intx,HBITMAPhBitmap);
  20. private:
  21. CDirector(){}
  22. private:
  23. //单件实例指针
  24. staticCDirector*m_pThisInst;
  25. //WINDOWS窗口句柄
  26. HWNDm_HWnd;
  27. //WINDOWSGDI绘图所用的设备上下文
  28. HDCm_HDC;
  29. }
  30. ;

可以看到,CDirector类是一个单例,我们为其创建了两个函数来进行绘制指定色的矩形和绘制位图的功能。没错,足够用了。

Director.cpp:

  1. #include"Director.h"
  2. CDirector*CDirector::m_pThisInst=NULL;
  3. CDirector*CDirector::GetInstance()
  4. {
  5. if(!m_pThisInst)
  6. {
  7. m_pThisInst=newCDirector;
  8. if(!m_pThisInst)returnNULL;
  9. m_pThisInst->Init(NULL);
  10. }
  11. returnm_pThisInst;
  12. }
  13. CDirector::~CDirector()
  14. {
  15. if(m_pThisInst)
  16. {
  17. deletem_pThisInst;
  18. m_pThisInst=NULL;
  19. }
  20. }
  21. voidCDirector::Init(HWNDhWnd)
  22. {
  23. if(hWnd)
  24. {
  25. m_HWnd=hWnd;
  26. m_HDC=::GetDC(m_HWnd);
  27. }
  28. }
  29. voidCDirector::FillRect(intx,COLORREFrgb)
  30. {
  31. HBRUSHhBrush=::CreateSolidBrush(rgb);
  32. RECTtRect;
  33. tRect.left=x;
  34. tRect.top=y;
  35. tRect.right=x+width;
  36. tRect.bottom=y+height;
  37. ::FillRect(m_HDC,&tRect,hBrush);
  38. ::DeleteObject(hBrush);
  39. }
  40. voidCDirector::DrawBitMap(intx,HBITMAPhBitmap)
  41. {
  42. HDChTempHDC=CreateCompatibleDC(m_HDC);
  43. HGDIOBJhOldObj=SelectObject(hTempHDC,hBitmap);
  44. BitBlt(m_HDC,x,y,width,height,hTempHDC,SRCCOPY);
  45. DeleteDC(hTempHDC);
  46. }

都是最基本的GDI绘制操作,这样我们的设备就建立好了。下面我们来创建场景。

Scene.h:

  1. #pragmaonce
  2. #include"Node.h"
  3. //==================================================================
  4. //File:Scene.h
  5. //Desc:场景类,用于管理所有的层
  6. //==================================================================
  7. //结点类
  8. classCScene:publicCNode
  9. {
  10. public:
  11. //构造
  12. CScene(constchar*szName);
  13. };

其对应的CPP:

  1. #include"Scene.h"
  2. //构造
  3. CScene::CScene(constchar*szName)
  4. {
  5. SetName(szName);
  6. }

没什么可解释的,就是一个结点类。然后是层:

Layer.h:

  1. #pragmaonce
  2. #include"Node.h"
  3. //==================================================================
  4. //File:Layer.h
  5. //Desc:层类,用于存放精灵
  6. //==================================================================
  7. //结点类
  8. classCLayer:publicCNode
  9. {
  10. public:
  11. //构造
  12. CLayer(constchar*szName);
  13. public:
  14. //更新
  15. virtualinlinevoidUpdate();
  16. //直接渲染
  17. virtualinlinevoidDraw();
  18. public:
  19. //设置颜色
  20. voidSetColor(COLORREFcolor);
  21. //取得颜色
  22. COLORREFGetColor();
  23. //设置高
  24. voidSetWidth(intvWidth);
  25. //取得宽
  26. intGetWidth();
  27. //设置高
  28. voidSetHeight(intvHeight);
  29. //取得高
  30. intGetHeight();
  31. private:
  32. //设置颜色
  33. COLORREFm_LayerColor;
  34. //宽度
  35. intm_nWidth;
  36. //高度
  37. intm_nHeight;
  38. };

可以看到,层有了宽高和颜色的设置,对应的Layer.cpp:

  1. #include"Layer.h"
  2. #include"Director.h"
  3. //构造
  4. CLayer::CLayer(constchar*szName):
  5. m_nWidth(0),
  6. m_nHeight(0)
  7. {
  8. SetName(szName);
  9. m_LayerColor=RGB(255,255,255);
  10. }
  11. //更新
  12. voidCLayer::Update()
  13. {
  14. CNode::Update();
  15. }
  16. //直接渲染
  17. voidCLayer::Draw()
  18. {
  19. stVec3tPos=GetWorldPos();
  20. CDirector::GetInstance()->FillRect(tPos.m_fX,tPos.m_fY,m_nWidth,m_nHeight,m_LayerColor);
  21. CNode::Draw();
  22. }
  23. //设置颜色
  24. voidCLayer::SetColor(COLORREFcolor)
  25. {
  26. m_LayerColor=color;
  27. }
  28. //取得颜色
  29. COLORREFCLayer::GetColor()
  30. {
  31. returnm_LayerColor;
  32. }
  33. //设置宽
  34. voidCLayer::SetWidth(intvWidth)
  35. {
  36. m_nWidth=vWidth;
  37. }
  38. //取得宽
  39. intCLayer::GetWidth()
  40. {
  41. returnm_nWidth;
  42. }
  43. //设置高
  44. voidCLayer::SetHeight(intvHeight)
  45. {
  46. m_nHeight=vHeight;
  47. }
  48. //取得高
  49. intCLayer::GetHeight()
  50. {
  51. returnm_nHeight;
  52. }


层已经可以显示了,通过取得设备并调用FillRect来显示一个色块矩形。最后我们来看一下精灵:

Sprite.h:

  1. #pragmaonce
  2. #include"Node.h"
  3. //==================================================================
  4. //File:Sprite.h
  5. //Desc:精灵类,用于显示图片
  6. //==================================================================
  7. //结点类
  8. classCSprite:publicCNode
  9. {
  10. public:
  11. //构造
  12. CSprite(constchar*szName);
  13. public:
  14. //更新
  15. virtualinlinevoidUpdate();
  16. //直接渲染
  17. virtualinlinevoidDraw();
  18. public:
  19. //设置位图句柄
  20. voidSetBitmap(HBITMAPvhBitmap);
  21. //设置位图句柄
  22. voidSetBitmap(HBITMAPvhBitmap,intvWidth,intvHeight);
  23. private:
  24. //所用位图句柄
  25. HBITMAPm_hBitmap;
  26. //位图宽度
  27. intm_nBitmapWidth;
  28. //位图高度
  29. intm_nBitmapHeight;
  30. };

我们为精灵增加了位图句柄,以使它可以绘制相应的位图。

Sprite.cpp:

  1. #include"Sprite.h"
  2. #include"Director.h"
  3. //构造
  4. CSprite::CSprite(constchar*szName):
  5. m_hBitmap(NULL),
  6. m_nBitmapWidth(0),
  7. m_nBitmapHeight(0)
  8. {
  9. SetName(szName);
  10. }
  11. //更新
  12. voidCSprite::Update()
  13. {
  14. CNode::Update();
  15. }
  16. //直接渲染
  17. voidCSprite::Draw()
  18. {
  19. if(m_hBitmap)
  20. {
  21. stVec3tPos=GetWorldPos();
  22. CDirector::GetInstance()->DrawBitMap(tPos.m_fX,m_nBitmapWidth,m_nBitmapHeight,m_hBitmap);
  23. }
  24. CNode::Draw();
  25. }
  26. //设置位图句柄
  27. voidCSprite::SetBitmap(HBITMAPvhBitmap)
  28. {
  29. BITMAPbmp;
  30. GetObject(vhBitmap,sizeof(BITMAP),&bmp);//得到一个位图对象
  31. m_hBitmap=vhBitmap;
  32. m_nBitmapWidth=bmp.bmWidth;
  33. m_nBitmapHeight=bmp.bmHeight;
  34. }
  35. //设置位图句柄
  36. voidCSprite::SetBitmap(HBITMAPvhBitmap,intvHeight)
  37. {
  38. m_hBitmap=vhBitmap;
  39. m_nBitmapWidth=vWidth;
  40. m_nBitmapHeight=vHeight;
  41. }

OK,就这样,我们就建立了一套可以进行场景,层,精灵管理和绘制的类。现在我们来具体的实现一下开始界面。我将开始界面分为



这里共有一个层和八个精灵。层嘛,就是一纯黑背景色块,八个精灵嘛,就如上图所示分别用来显示不同的位图:




我们现在打开程序的主源文件ShowNodeTree.cpp,在文件顶部加入:

  1. #include"Sprite.h"
  2. #include"Layer.h"
  3. #include"Scene.h"
  4. #include"Director.h"
  5. //唯一使用的场景
  6. CScene*g_pMyScene=NULL;

并在InitInstance函数的尾部加入:

  1. //初始化设备
  2. CDirector::GetInstance()->Init(hWnd);
  3. //增加层
  4. CLayer*pNewLayer=newCLayer("Layer1");
  5. pNewLayer->SetPos(100,40,0);
  6. pNewLayer->SetColor(RGB(0,0));
  7. pNewLayer->SetWidth(497);
  8. pNewLayer->SetHeight(434);
  9. //增加精灵
  10. charszCurrDir[_MAX_PATH];
  11. ::GetCurrentDirectory(_MAX_PATH,szCurrDir);
  12. charszImagePathName[_MAX_PATH];
  13. wsprintf(szImagePathName,"%s\\knm.bmp",szCurrDir);
  14. HBITMAPhbmp=(HBITMAP)LoadImage(NULL,szImagePathName,IMAGE_BITMAP,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
  15. CSprite*pNewSprite=newCSprite("knm");
  16. pNewSprite->SetBitmap(hbmp);
  17. pNewSprite->SetPos(130,0);
  18. //将精灵放入到层
  19. pNewLayer->AddChild(pNewSprite);
  20. wsprintf(szImagePathName,"%s\\logo.bmp",szCurrDir);
  21. hbmp=(HBITMAP)LoadImage(NULL,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
  22. CSprite*pNewSprite2=newCSprite("logo");
  23. pNewSprite2->SetBitmap(hbmp);
  24. pNewSprite2->SetPos(90,100,0);
  25. //将精灵放入到层
  26. pNewLayer->AddChild(pNewSprite2);
  27. wsprintf(szImagePathName,"%s\\player.bmp",LR_LOADFROMFILE|LR_CREATEDIBSECTION);
  28. CSprite*pNewSprite3=newCSprite("player");
  29. pNewSprite3->SetBitmap(hbmp);
  30. pNewSprite3->SetPos(260,230,0);
  31. //将精灵放入到层
  32. pNewLayer->AddChild(pNewSprite3);
  33. wsprintf(szImagePathName,"%s\\menu_title.bmp",szCurrDir);
  34. hbmp=(HBITMAP)LoadImage(NULL,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
  35. CSprite*pNewSprite4=newCSprite("menu_title");
  36. pNewSprite4->SetBitmap(hbmp);
  37. pNewSprite4->SetPos(40,270,0);
  38. //将精灵放入到层
  39. pNewLayer->AddChild(pNewSprite4);
  40. wsprintf(szImagePathName,"%s\\menu_1.bmp",LR_LOADFROMFILE|LR_CREATEDIBSECTION);
  41. CSprite*pNewSprite5=newCSprite("menu_1");
  42. pNewSprite5->SetBitmap(hbmp);
  43. pNewSprite5->SetPos(100,310,0);
  44. //将精灵放入到层
  45. pNewLayer->AddChild(pNewSprite5);
  46. wsprintf(szImagePathName,"%s\\menu_2.bmp",LR_LOADFROMFILE|LR_CREATEDIBSECTION);
  47. CSprite*pNewSprite6=newCSprite("menu_2");
  48. pNewSprite6->SetBitmap(hbmp);
  49. pNewSprite6->SetPos(100,350,0);
  50. //将精灵放入到层
  51. pNewLayer->AddChild(pNewSprite6);
  52. wsprintf(szImagePathName,"%s\\menu_cursor.bmp",LR_LOADFROMFILE|LR_CREATEDIBSECTION);
  53. CSprite*pNewSprite7=newCSprite("menu_cursor");
  54. pNewSprite7->SetBitmap(hbmp);
  55. pNewSprite7->SetPos(60,0);
  56. //将精灵放入到层
  57. pNewLayer->AddChild(pNewSprite7);
  58. wsprintf(szImagePathName,"%s\\copyright.bmp",LR_LOADFROMFILE|LR_CREATEDIBSECTION);
  59. CSprite*pNewSprite8=newCSprite("copyright");
  60. pNewSprite8->SetBitmap(hbmp);
  61. pNewSprite8->SetPos(120,390,0);
  62. //将精灵放入到层
  63. pNewLayer->AddChild(pNewSprite8);
  64. //将层放入场景
  65. g_pMyScene=newCScene("HDL");
  66. g_pMyScene->AddChild(pNewLayer);
  67. //设定每毫秒刷新一帧
  68. ::SetTimer(hWnd,1,20,NULL);

看,经过上面的代码之后,我们就创建了相应的层,精灵和场景。最后创建一个定时器来进行屏幕重绘,FPS嘛,就设为50好了。

我们在窗口消息处理函数中加入:

  1. caseWM_PAINT:
  2. {
  3. hdc=BeginPaint(hWnd,&ps);
  4. //TODO:在此添加任意绘图代码..
  5. if(g_pMyScene)
  6. {
  7. //更新和绘制场景
  8. g_pMyScene->Update();
  9. g_pMyScene->Draw();
  10. }
  11. EndPaint(hWnd,&ps);
  12. }
  13. break;
  14. caseWM_TIMER:
  15. {
  16. //让场景的Layer1层不断向右移动,到像素时重置。
  17. if(g_pMyScene)
  18. {
  19. CNode*pLayer=g_pMyScene->QueryChild("Layer1");
  20. stVec3tPos=pLayer->GetWorldPos();
  21. tPos.m_fX+=1;
  22. if(tPos.m_fX>400)
  23. {
  24. tPos.m_fX=0;
  25. }
  26. pLayer->SetPos_X(tPos.m_fX);
  27. }
  28. //响应刷新
  29. ::InvalidateRect(hWnd,NULL,TRUE);
  30. }
  31. break;
  32. caseWM_KEYDOWN:
  33. {
  34. if(wParam==VK_UP)
  35. {//按上时选人菜单光标置在第一项前面。
  36. if(g_pMyScene)
  37. {
  38. CSprite*pNewSprite7=(CSprite*)g_pMyScene->QueryChild("menu_cursor");
  39. if(pNewSprite7)
  40. {
  41. pNewSprite7->SetPos(60,0);
  42. }
  43. }
  44. }
  45. if(wParam==VK_DOWN)
  46. {//按下时选人菜单光标置在第二项前面。
  47. if(g_pMyScene)
  48. {
  49. CSprite*pNewSprite7=(CSprite*)g_pMyScene->QueryChild("menu_cursor");
  50. if(pNewSprite7)
  51. {
  52. pNewSprite7->SetPos(60,0);
  53. }
  54. }
  55. }
  56. }
  57. break;
  58. caseWM_DESTROY:
  59. //当窗口销毁时也一并删除定时器并释放场景。
  60. ::KillTimer(hWnd,1);
  61. if(g_pMyScene)
  62. {
  63. //会调用CNode的虚析构函数释放所有子结点。所以不会造成内存泄漏。
  64. deleteg_pMyScene;
  65. g_pMyScene=NULL;
  66. }
  67. PostQuitMessage(0);
  68. break;

这样我们的开始界面就算完成了,编译运行一下吧:



怎么样?不错吧。一个开始界面层展现在我们面前,所有精灵做为层的子结点而随着层保持运动。虽然这种方式还有一些闪屏,但,那并不是重点,关键是你彻彻底底的理解了结点系统对于引擎架构的作用和设计思想。好了,喝口水歇一会儿开始进入到Cocos2d-x中去看看。

2.1.2 Cocos2d-x中的精灵,层,场景与结点:


在Cocos2d-x中,结点的基类是CCNode,它的实现远远超越了上面结点代码的复杂度,不过没关系,随着后面相关代码接触的加深,你可以很明白它的全部接口函义,但现在,你所需要的只是明白它就不过是个结点,它不过是咱们上面结点类的演变,说的通俗点:不要以为你穿个马甲哥就认不出你了!

在CCNode中,有一个指针容器成员m_pChildren ,它存放了当前结点下的所有子结点,我们通过addChild来增加子结点到其中。我们并没有发现所谓的兄弟结点,为什么呢?那时因为兄弟结点被“扁平化”处理了。为了提升效率,减少递归调用的次数,可以将所有子结点的指针都存放在当前结点的容器中,所以子结点的兄弟结点就不必出现了。

有了结点CCNode,我们来看一下精灵CCSprite,它在libcocos2d的sprite_nodes分类下。

打开CCSprite.h:

CCSprite :publicCCNode,public CCTextureProtocol,public CCRGBAProtocol

很明显,精灵是由结点CCNode派生出来的子类。它的主要功能就是显示图形。在其函数中,涉及纹理加载和OpenGL相关的顶点和颜色,纹理寻址的操作。

层CCLayer和场景CCScene是被存放在libcocos2d的layers_scenes_transitions_nodes分类下。

打开CCLayer.h:

CC_DLLCCLayer : public CCNode,public CCTouchDelegate,publicCCAccelerometerDelegate,publicCCKeypadDelegate

可以看到,CCLayer除了由结点CCNode派生外,还增加了用户输入事件的响应接口。如CCTouchDelegate是触屏事件响应接口类,CCAccelerometerDelegate是加速键消息事件响应接口类,CCKeypadDelegate是软键盘消息事件响应接口类。

打开CCScene.h:

class CC_DLL CCScene :publicCCNode

好吧,真是简单明了,场景就是专门管理子结点的,或者说就是专门管理层结点的。

现在我们来看一些它们的具体应用。

打开HelloCpp工程。在Classes下我们看到有两个类:

1 . AppDelegate:由CCApplication派生,即Cocos2d-x的程序类。可以把它当作上面图示中的”Root”。它的作用就是启动一个程序,创建主窗口并初始化游戏引擎并进入消息循环。

2 . HelloWorld:由CCLayer派生,即Cocos2d-x的层。对应上面图示中“开始界面”场景中的“界面层”。它的作用是显示背景图和菜单及退出按钮等精灵。在这个类里有一个静态函数HelloWorld::scene()创建了所用到的场景并创建HelloWorld这个层放入到场景中。

在程序的main函数中创建了AppDelegate类的实例对象并调用run运行。 之后会在AppDelegate的函数applicationDidFinishLaunching(代表程序启动时的处理)中结尾处调用HelloWorld::scene()创建了场景。

游戏运行起来是个什么样子呢?没错,我看跟魂斗罗的“开始界面”也差不到哪去嘛。当然,只是指组织关系。



嗯,到此,本节的知识算是讲述完毕!做为一个有上进心的程序员,咱们来做些课后题吧?

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


    本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
 下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是