Cocos2d-x从入门到精通第五课《Cocos2d-x中的Node》

Cocos2d-x中的Node

视频教程地址:http://edu.csdn.net/course/detail/1342/20983?auto_start=1

一.什么是结点

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

定义:

 

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

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

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

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

如图A:

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

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

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

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

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

如图B:

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

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

试想一下:

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

二.实现自己的结点

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

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

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

#ifndef __HelloWorld__CNode__
#define __HelloWorld__CNode__

#include <stdio.h>
using namespace std;
//结点类
class CNode
{
public:

    //构造
    CNode();
    //析构
    virtual ~CNode();

public:

    //更新
    virtual  inline void    Update();
    //渲染
    virtual  inline void    Draw();

public:
    //设置当前结点名称
    void    SetName(const char* szName);
    //取得当前结点名称
    const string&   GetName();

    //加入一个子结点类
    void    AddChild(CNode* pChildNode);
    //取得子结点
    CNode*  GetFirstChild();

    //加入一个兄弟结点类
    void    AddBorther(CNode* pBortherNode);
    //取得兄弟结点
    CNode*  GetFirstBorther();

    //删除一个结点
    bool    DelNode(CNode*  pNode);
    //清空所有子结点
    void    DelAllChild();
    //清空所有兄弟结点
    void    DelAllBorther();

    //查询某个子结点-- 纵向查询
    CNode*  QueryChild(const char* szName);
    //查询某个兄弟结点-- 横向查询
    CNode*  QueryBorther(const char* szName);
    //为了方便检测结点树系统的创建结果,这里增加了一个保存结点树到XML文件的函数。
    bool    SaveNodeToXML(const char* szXMLFile);

protected:

    //设置父结点
    void    SetParent(CNode* pParentNode);
    //取得父结点
    CNode*  GetParent();

    //保存结点树到XML文件,这个函数是只生成本结点的信息。
    bool    SaveNodeToXML(FILE* hFile);
private:

    //当前结点名称
    string  m_strNodeName;
    //父结点
    CNode*  m_pParentNode;
    //第一个子结点
    CNode*  m_pFirstChild;
    //第一个兄弟结点
    CNode*  m_pFirstBorther;
}
;
#endif /* defined(__HelloWorld__CNode__) */
</pre><p></p><p></p><p><span style="white-space:pre"></span>cpp文件,代码如下:</p><p></p><p> </p><pre name="code" class="cpp">#include "CNode.h"
//构造
CNode::CNode()
{
    m_strNodeName[0] = '\0';
    m_pParentNode = NULL;
    m_pFirstChild = NULL;
    m_pFirstBorther = NULL;
}

//析构
CNode::~CNode()
{
    DelAllChild();
}

//更新
void    CNode::Update()
{
    if(m_pFirstChild)
    {
        m_pFirstChild->Update();
    }
    //在这里增加你更新结点的处理
    //…
    if(m_pFirstBorther)
    {
        m_pFirstBorther->Update();
    }
}
//直接渲染
void    CNode::Draw()
{
    if(m_pFirstChild)
    {
        m_pFirstChild->Draw();
    }
    //在这里增加你渲染图形的处理
    //…
    if(m_pFirstBorther)
    {
        m_pFirstBorther->Draw();
    }
}
//设置当前结点名称
void    CNode::SetName(const char* szName)
{
    m_strNodeName = szName ;
}
//取得当前结点名称
const string& CNode::GetName()
{
    return m_strNodeName;
}
//加入一个子结点类
void    CNode::AddChild(CNode*  pChildNode)
{
    if(pChildNode)
    {
        if(m_pFirstChild)
        {
            m_pFirstChild->AddBorther(pChildNode);
        }
        else
        {
            m_pFirstChild = pChildNode;
            m_pFirstChild->SetParent(this);
        }
    }
}
//取得子结点
CNode*  CNode::GetFirstChild()
{
    return m_pFirstChild ;
}

//加入一个兄弟结点类
void    CNode::AddBorther(CNode* pBortherNode)
{
    if(pBortherNode)
    {
        if(m_pFirstBorther)
        {
            m_pFirstBorther->AddBorther(pBortherNode);
        }
        else
        {
            m_pFirstBorther = pBortherNode;
            m_pFirstBorther->SetParent(m_pParentNode);
        }
    }
}
//取得兄弟结点
CNode*  CNode::GetFirstBorther()
{
    return m_pFirstBorther ;
}
//删除一个子结点类
bool    CNode::DelNode(CNode*   pTheNode)
{
    if(pTheNode)
    {
        if(m_pFirstChild)
        {
            if(m_pFirstChild == pTheNode)
            {
                m_pFirstChild = pTheNode->GetFirstBorther();
                delete pTheNode;
                return true;
            }
            else
            {
                if(true == m_pFirstChild->DelNode(pTheNode))return true;
            }
        }
        if(m_pFirstBorther)
        {
            if(m_pFirstBorther == pTheNode)
            {
                m_pFirstBorther = pTheNode->GetFirstBorther();
                delete pTheNode;
                return true;
            }
            else
            {
                if(true == m_pFirstBorther->DelNode(pTheNode))return true;
            }
        }
    }
    return false;
}
//清空所有子结点
void    CNode::DelAllChild()
{
    if(m_pFirstChild)
    {
        CNode * pBorther = m_pFirstChild->GetFirstBorther();
        if(pBorther)
        {
            pBorther->DelAllBorther();
            delete pBorther;
            pBorther = NULL;
        }
        delete m_pFirstChild ;
        m_pFirstChild = NULL;
    }
}

//清空所有兄弟结点
void    CNode::DelAllBorther()
{
    if(m_pFirstBorther)
    {
        m_pFirstBorther->DelAllBorther();
        delete m_pFirstBorther;
        m_pFirstBorther = NULL;
    }
}

//查询某个子结点-- 纵向查询
CNode*  CNode::QueryChild(const char* szName)
{
    if(szName)
    {
        if(m_pFirstChild)
        {
            //如果是当前子结点,返回子结点。
            if(0 == strcmp(szName,m_pFirstChild->GetName().c_str()))
            {
                return m_pFirstChild;
            }
            else
            {
                //如果不是,查询子结点的子结点。
                CNode*  tpChildChild = m_pFirstChild->QueryChild(szName);
                if(tpChildChild)
                {
                    return tpChildChild ;
                }
                //如果还没有,查询子结点的兄弟结点。
                return  m_pFirstChild->QueryBorther(szName);
            }
        }
    }
    return NULL;
}

//查询某个兄弟结点-- 横向查询
CNode*  CNode::QueryBorther(const char* szName)
{
    if(szName)
    {
        if(m_pFirstBorther)
        {
            if(0 == strcmp(szName,m_pFirstBorther->GetName().c_str()))
            {
                return m_pFirstBorther;
            }
            else
            {
                //如果不是,查询子结点的子结点。
                CNode*  tpChildChild = m_pFirstBorther->QueryChild(szName);
                if(tpChildChild)
                {
                    return tpChildChild ;
                }
                return  m_pFirstBorther->QueryBorther(szName);
            }
        }
    }
    return NULL;
}

//设置父结点
void    CNode::SetParent(CNode* pParentNode)
{
    m_pParentNode = pParentNode ;
}

//取得父结点
CNode*  CNode::GetParent()
{
    return m_pParentNode ;
}

//保存结点树到XML
bool    CNode::SaveNodeToXML(const char* szXMLFile)
{
    FILE*       hFile = fopen(szXMLFile,"wt");
    if(!hFile)
    {
        return false;
    }
//    fprintf(hFile,TEXT("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"));
//    fprintf(hFile,TEXT("<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"));
//    fprintf(hFile,TEXT("<!--Honghaier Game Tutorial -->\n"));
//
//    fprintf(hFile,TEXT("<plist version=\"1.0\">\n"));
//    fprintf(hFile,TEXT("<dict>\n"));
//    //========================================================
//    fprintf(hFile,TEXT("<key>NodeTree</key>"));
//    fprintf(hFile,TEXT("<dict>"));
//    if(false == SaveNodeToXML(hFile))
//    {
//        fclose(hFile);
//        return false;
//    }
//
//    fprintf(hFile,TEXT("</dict>"));
//    //========================================================
//    fprintf(hFile,TEXT("</dict>"));
//    fprintf(hFile,TEXT("</plist>\n"));
//    fclose(hFile);
    return true;
}
//保存结点树到XML
bool    CNode::SaveNodeToXML(FILE*  hFile)
{
    //========================================================
    //fprintf(hFile,TEXT("<key>NodeName</key>"));
    //fprintf(hFile,TEXT("<string>%s</string>"),m_strNodeName.c_str());
//    fprintf(hFile,TEXT("<key>%s</key>"),m_strNodeName.c_str());
//    //========================================================
//    fprintf(hFile,TEXT("<dict>"));
//    if(m_pFirstChild)
//    {
//        if(false == m_pFirstChild->SaveNodeToXML(hFile))
//        {
//            fclose(hFile);
//            return false;
//        }
//    }
//    fprintf(hFile,TEXT("</dict>"));
//
//    if(m_pFirstBorther)
//    {
//        if(false == m_pFirstBorther->SaveNodeToXML(hFile))
//        {
//            fclose(hFile);
//            return false;
//        }
//    }
    return true;
}

二.Cocos2d-x中的精灵,层,场景与Node

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

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

有了结点Node,我们来看一下精灵Sprite,它在2d文件夹下的的sprite_nodes分类下。

打开CCSprite.h:

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

层Layer和场景Scene是被存放在2d文件夹的layers_scenes_transitions_nodes分类下。

打开CCLayer.h:

可以看到,Layer由结点Node派生,也就是说Layer也是一个Node的子类。

还是在同级目录打开CCScene.h:

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

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

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

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

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

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

游戏运行起来是个什么样子呢?

三.Node的渲染流程

这里我们来简单讲解一下渲染流程,只讲解渲染流程,具体最后是怎么绘制那就需要讲解OpenGL的绘制原理了,也就说具体怎么绘制就脱离了我们课程的规划了,所以这里只讲解渲染流程,如果你很想学习OpenGL的渲染原理可以咨询我们的全日制课程,在全日制课程中所有的内容都有哦。

首先找到cocos2d工程中的CCApplition-mac.h文件,找到run()方法,进入该方法的实现,可以看到该函数实现的代码如下:

其他的代码我们这里先不用多想,直接进入红色矩形标注的代码,进入mainLoop()的实现,下面我们通过一个UML流程图图来看一下渲染流程的函数调用流程,UML流程图如下:

通过该流程图我们可以清晰的看出该Cocos2d-x渲染的过程函数的调用,最后通过调用各自渲染类型的相应函数函数来完成渲染。在本节课的视频教程中我们会带领大家好好看看渲染流程的代码,并会做出详细解释,让大家更加明白渲染的流程机制。

四.作业:

1,跟着我们Node的代码,实现自己的一个Node。

2,跟着渲染流程图查看相关源码,认识Cocos2d-x的渲染流程

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-01 03:03:05

Cocos2d-x从入门到精通第五课《Cocos2d-x中的Node》的相关文章

Cocos2d-x从入门到精通第四课 -- 《Cocos2d-x中的FileUtils》d

FileUtils的作用 课程视频教程地址:http://edu.csdn.net/course/detail/1342/20982?auto_start=1 FileUtils是Cocos2d-x里面的文件管理类.它对我们游戏中的资源文件起到管理的作用,可以说是游戏资源管理的大管家.FileUtils可以进行读写文件,可以设置可搜索路径,可以获得资源文件的绝对和相对路径,可以判断文件和文件夹是否存在,可以获得资源文件的大小,等.很多对文件操作的所需的功能FileUtils都有对应的接口.所以说

GPU 编程入门到精通(五)之 GPU 程序优化进阶

博主因为工作其中的须要,開始学习 GPU 上面的编程,主要涉及到的是基于 GPU 的深度学习方面的知识.鉴于之前没有接触过 GPU 编程.因此在这里特地学习一下 GPU 上面的编程. 有志同道合的小伙伴,欢迎一起交流和学习.我的邮箱: [email protected] .使用的是自己的老古董笔记本上面的 Geforce 103m 显卡,尽管显卡相对于如今主流的系列已经很的弱,可是对于学习来说.还是能够用的.本系列博文也遵从由简单到复杂,记录自己学习的过程. 0. 文件夹 GPU 编程入门到精通

《ASP.NET SignalR系列》第五课 在MVC中使用SignalR

接着上一篇:<ASP.NET SignalR系列>第四课 SignalR自托管(不用IIS) 一.概述 本教程主要阐释了如何在MVC下使用ASP.NET SignalR. 添加SignalR库到MVC中. 创建hub和OWIN startup 类来推送内容到客户端. 在页面中使用SignalR jQuery 库发送消息和呈现从来得更新. 下面屏幕截图展示了一个完成的聊天应用程序 二.创建项目 1.用MVC5 .NET4.5 创建一个名为SignalRChat的项目 2.改变授权. 3.选择 N

【PHP】最详细PHP从入门到精通(五)——PHP错误处理

 PHP从入门到精通 之PHP中的字符串 在创建脚本和 web 应用程序时,错误处理是一个重要的部分.如果您的代码缺少错误检测编码,那么程序看上去很不专业,也为安全风险敞开了大门. 本教程介绍了 PHP 中一些最为重要的错误检测方法. 我为大家讲解不同的错误处理方法: PHP中的错误处理 1.PHP的错误级别:见表格. 2.调整PHP错误报告级别: PHP中,调整错误报告级别的方式有两种: ①修改PHP.ini文件的配置项. a.会导致在当前服务器环境下所有PHP文件都受其影响. b.如果代码更

Cocos2d-x从入门到精通第六课《自定义绘制》

课程视频教程地址:http://edu.csdn.net/course/detail/1342/20984?auto_start=1 一.自定义绘制 一个图形引擎,总是由构建点,线,面的绘制功能写起来的.点,线,面.构成了最初的图形基础.所以说,掌握点,线,面是掌握引擎的基础. Cocos2d-x 2.0的时候开始有的使用DrawPrimitives命名空间下的相关函数进行绘制.Cocos2d-x3.0版本开始有的DrawNode类提供的相关方法来绘制.今天的课程主要讲解使用最新的方法进行绘制.

Linux自有服务(1)-Linux从入门到精通第五天

文章大纲 一.运行模式二.用户与用户组管理(重点)三.网络设置四.ssh服务(重点)五.学习资料下载六.参考文章 自有服务,即不需要用户独立去安装的软件的服务,而是当系统安装好之后就可以直接使用的服务(内置). 一.运行模式 运行模式也可以称之为运行级别.在linux中存在一个进程:init (initialize,初始化),进程id是1.查看进程:#ps -ef|grep init 该进程存在一个对应的配置文件:inittab(系统运行级别配置文件,位置/etc/inittab)文件的主要内容

react.js从入门到精通(五)——组件之间的数据传递

一.组件之间在静态中的数据传递 从上面的代码我们可以看出,两个组件之间的数据是分离,但如果我们有某种需求,将数据从一个组件传到另一个组件中,该如何实现? 场景设计: 将Home.js中的HomeData传递到MyScreen.js中 import React,{ Component } from 'react' import MyScreen from "./MyScreen"; class Home extends Component { constructor(props) { s

git从入门到精通(五)(新建分支,合并分支)

1. git branch : 查看分支 2. git branch xuzhiwen :  创建分支 3. git checkout xuzhiwen : 切换分支 4. 切换分支需要做的操作:  git add , git commit 5. 合并分支需要的操作: a.先切换到master 分支 b. git merge xuzhiwen  : 合并分支 原文地址:https://www.cnblogs.com/beibidewomen/p/9687964.html

网络安全从入门到精通 (第五章-2) MySQL注入 — Dns 注入

本文内容: DNSLOG的函数解析 UNC路径 DNSLOG的使用场景 DNSLOG注入的具体流程 1,DNSLOG的函数解析: Mysql函数:LOAD_FILE()读取文件的函数. 读取字符串并返回文档内容未字符串,要使用次函数,文件必须位于服务器主机上,必须指定完整路径的文件,而且必须有FILE权限. 该文件所有字节可读,但文件内容必须小于max_allowed_packet(限制server接收的数据包大小函数,默认1MB). 如果该文件不存在或者无法读取,因为前面的条件之一不满足,函数