欢迎来到zhooyu的专栏。
1、本节要实现的内容
上节课我们已经创建了一个基础Object类,以后所有的游戏元素都可以从这个基类中派生出来。同时为了操作方便,我们可以为任意两个Object类(及其派生类)的实例对象添加一种父子关系,后期通过父物体与子物体关系,能够轻松的实现游戏物体的操控,方便我们在游戏中对所有游戏元素的组织和管理。再比如后期加父子关系后,就可以同步控制父子物体在三维空间的位置、旋转和缩放操作。
2、父子物体的概念
上节课介绍了Object类,我们知道所有可以由Object类派生出来,这是类之间的继承与被继承的关系(指的是类)。同时,我们还可以将具体类的实例附加一种父子关系概念(指的是具体的实例)。父子关系,就是假设我们有两个Object实例B和实例A,这两个实例对应的类都是Object类,那么我们就可以指定A和B的父子关系,那么我们可以指定A是父物体B是子物体,也可以指定B是父体A是子物体。也就是指定一个物体添加为另一个物体的子物体,此时我们称两个实例建立了父子关系。Object是我们游戏里边绝大部分类的根类。
3、父子物体模式的优点
父物体通过一个链表,可以保存他所有的子物体,但每一个子物体只能有一个父物体,这样就构造出了一个简单的树状结构。我们可以通过链表的方式,物体添加、删除、遍历的它的物体。
3.1、组成一个树状结构管理模式
像许多游戏里头一样,有些场景它是有一个MainSence根节点。其他所有的游戏元素都是连接到这个游戏MainSence的子节点上去,当然也可以是子节点的子节点,以此类推。就是说后期我们如果想给游戏中添加任何元素,就只用给这个这个MainSence实例添加子节点就可以了。我们游戏中Object对象之间可以以对象树的形式组织起来的。子对象就会加入到父对象的一个成员变量叫Child(孩子)的List(列表)中,我们程序中每个Object类都有一个ChildNodeList列表,用于存储当前物体的所有子类实例,同时每个子类实例都有一个Object类型的ptParent指针,用于存储唯一的父物体指针。当然如果当前物体不存在父物体(实例)时,这个父指针也可以为空。当父对象析构的时候,这个列表ChildNodeList中的所有子对象也会被析构。(注意,这里是说父对象和子对象,不要理解成父类和子类)。我们今后所有创建的Object类或继承类的实例也可以也继承了这种对象树关系。一个孩子可以添加成为父组件的一个子组件。
3.2、方便的内存管理模式
我们向某个Object对象实例中添加了Object对象实例(建立父子关系),当我们删除父子关系时,我们的DelChild函数或DelAllChild默认情况下会将子对象实例析构,同时我们子物体在析构过程中,子物体的子物体以及,子子子物体均会一同被析构,确保内存能及时释放,避免内存溢出问题。这个结果也是我们开发人员所期望的。比如,如果有一个窗口和窗口中的一个按钮,我们建立父子关系后,窗口可以添加按钮为子物体。后期当我们用窗口的DelChild函数删除和按钮的父子关系时,其所在的窗口会自动将该按钮从其子对象列表(ChildNodeList)中删除,并析构这个按键释放内存,按钮在屏幕上消失。当这个窗口析构的时候,窗口ChildNodeList列表里边其他所有的子物体也会自动释放内存。引入对象树的概念,在一定程度上解决了内存问题。
3.3、父物体可以控制子物体的移动旋转及缩放
因为我们设置了负物体与子物体的从属关系。我们可以方便的控制他们之间的Transform操作。后期我们添加相应功能后,当父体移动时,子物体也会随之移动。但是反过来子物体移动时副物体不需要移动。这种模式在旋转和缩放中同样适用。这个场景其实比较常用。比如说我们比如说我们去超市推了一个购物车,装上一些商品后,购物车和商品就构成了简单的父子关系。购物车当做父物体,车里的商品当成是子物体。那么我们推着购物车移动时,所有的子物体商品会随着我们的购物车父物体移动或旋转。但是,如果你仅仅摆动购物车里的商品时,购物车是不会随之移动的。日常生活中遍及着各种这样的父子关系模式,比如汽车拉货物,电梯和电梯里乘坐的人的关系,书包和书包中的书的关系。
4、链表的实现
要实现对象树概念,我们就必须有一个链表来实现功能。当然,我们可以直接使用STL中的List容器,功能基本一致。但为了我们后期操作的灵活性和代码清晰度,我们自己准备了一个链表,我们这里实现的是不带头双向不循环列表。
4.1、存储实例的指针
我们这个ChildNodeList链表中存储的是类实例的指针,也就是说这个链表只负责保存父子关系。具体的实物创建需要单独创建,这个对象实物可以是静态定义的,也可以是动态创建的。不管是动态创建还是静态定义的,我们都可以将它的指针通过AddChild函数添加到父物体的子链表中,让他们之间形成父子从属关系。
4.2、使用了双向链表模板
为了操作方便,我们使用了不带头双向不循环列表。但同时还有一个问题,由于我们Object拥有一个子链表,链表中的保存的指针又是Object类型的,因此我们必须使用C++模板template来定义Node和NodeList类,并创建这个ChildNodeList链表,否则会出现前后创建逻辑顺序错误。采用双向链方式表主要是为了查询、操作方便。
4.3、方便后期拓展
这里我们知道STL有现成的链表,但是我们没有使用,给自己写一个链表,主要是为了能够更加自主的去操控链表。大家如果为了方便,也可以使用STL有现成的链表,不过它是一个“带头双向循环链表”。同时我们还在可以在后期扩展链表的使用方法,方便我们的操作。
//用于统计所有创建的物体个数,动态创建节点个数int st_NodeCreateNum=0;//用于统计所有创建的物体个数,动态删除节点个数int st_NodeDeleteNum=0;//用于统计所有创建的物体个数,显示调试信息float ShowNodeStatistic(HWND hWnd,HDC hDC,float x,float y,float h=20);//重置统计数据void ResetNodeStatistic();//自定义节点(模板)template<typename Type>struct Node
{public://存储链表的下一指针Node *ptNextNode;//存储链表的上一指针Node *ptPrevNode;//存储链表的当前物体指针Type *ptInstance;//初始化结构体Node(){ptNextNode=NULL;ptPrevNode=NULL;ptInstance=NULL;}};//自定义双向链表(模板)template<typename Type>class NodeList
{public://指针链表首部Node<Type> *ptNodeHead;//指针链表尾部Node<Type> *ptNodeTail;//记录链表的子节点个数int iNodeAmount;public://构造函数NodeList();//从链表尾部添加一个保存特定实例指针的子节点Type *AddNode(Type *ptTempInstance);//从链表尾部删除一个保存特定实例指针的子节点Type *DelNode();//从链表中删除特定实例指针的一个子节点Type *DelNode(Type *ptTempInstance);//非递归显示显示所有子节点概要信息void ShowNodeList(HWND hWnd,HDC hDC,float x,float y,float h=20);};
float ShowNodeStatistic(HWND hWnd,HDC hDC,float x,float y,float h)
{int line=0;//显示坐标信息文字glColor3f(1,0,0);//显示子物体链表char szTemp[1024]="";//显示统计信息glWindowPos2f(x,y-h*line++);sprintf(szTemp,"st_NodeCreateNum:%d",st_NodeCreateNum);drawString(hDC,szTemp);//显示统计信息glWindowPos2f(x,y-h*line++);sprintf(szTemp,"st_NodeDeleteNum:%d",st_NodeDeleteNum);drawString(hDC,szTemp);//返回显示后的纵坐标return y-h*line;}//重置统计数据void ResetNodeStatistic()
{//用于统计所有创建的物体个数,动态创建节点个数st_NodeCreateNum=0;//用于统计所有创建的物体个数,动态删除节点个数st_NodeDeleteNum=0;}//构造函数template<typename Type>NodeList<Type>::NodeList()
{ptNodeHead=NULL;ptNodeTail=NULL;iNodeAmount=0;}//从链表头部添加一个保存特定实例指针的子节点template<typename Type>Type *NodeList<Type>::AddNode(Type *ptTempInstance)
{//待添加实例指针不能为空if(ptTempInstance!=NULL){//动态创建链表项Node<Type> *ptTempNode=new Node<Type>;//保存实例指针到节点中ptTempNode->ptInstance=ptTempInstance;//将动态创建的链表节点添加到链表中if(ptNodeHead==NULL){//插入链表节点,如果链表为空则直接添加ptNodeHead=ptTempNode;ptNodeTail=ptTempNode;}else{//如果链表不为空,则将动态创建的节点保存到链表的尾部ptTempNode->ptPrevNode=ptNodeTail;//将子类保存到新创建的链表项指针中ptNodeTail->ptNextNode=ptTempNode;//更新链表尾部指针ptNodeTail=ptTempNode;}//统计信息st_NodeCreateNum++;//统计子节点个数iNodeAmount++;}//返回节点内容的实例指针return ptTempInstance;}//从链表尾部删除一个保存特定实例指针的子节点template<typename Type>Type *NodeList<Type>::DelNode()
{//链表节点不能为空if(ptNodeTail!=NULL){//从尾部删除动态节点Node<Type> *ptTempNode=ptNodeTail;//保存待删除节点内容的实例指针Type *ptTempInstance=ptNodeTail->ptInstance;//从尾部删除节点if(ptNodeTail->ptPrevNode==NULL){//重置尾部节点指针ptNodeHead=NULL;ptNodeTail=NULL;}else{//删除尾部子节点指针ptNodeTail->ptPrevNode->ptNextNode=NULL;//设置新的尾部节点指针ptNodeTail=ptNodeTail->ptPrevNode;}//从尾部删除动态节点delete ptTempNode;//统计信息st_NodeDeleteNum++;//统计子节点个数iNodeAmount--;//返回待删除节点内容的实例指针return ptTempInstance;}return NULL;}//从链表中删除特定实例指针的一个子节点template<typename Type>Type *NodeList<Type>::DelNode(Type *ptTempInstance)
{//待删除实例指针不能为空if(ptTempInstance!=NULL){//链表不能为空if(ptNodeHead!=NULL){//头部节点作为循环的开始节点Node<Type> *ptTempNode=ptNodeHead;//遍历链表项,删除指定内容的节点while(ptTempNode!=NULL){if(ptTempNode->ptInstance==ptTempInstance){//当前待删除节点在头部的情况if(ptTempNode==ptNodeHead){//判断是否只有一个节点if(ptTempNode->ptNextNode==NULL){//如果只有一项待删除,则置空头部和尾部指针ptNodeHead=NULL;ptNodeTail=NULL;}else{//如果有两项及以上节点,将头部指针指向下一个节点ptNodeHead=ptTempNode->ptNextNode;//将当前头节点的上一项指针重置为空ptNodeHead->ptPrevNode=NULL;}}else{//当前待删除节点不在头部的情况if(ptTempNode==ptNodeTail){//当前待删除节点在尾部的情况,尾部指针指向待删除节点的前一个节点ptNodeTail=ptTempNode->ptPrevNode;//当前待删除节点在尾部的情况,将末尾的节点下一项指针置空删除掉ptNodeTail->ptNextNode=NULL;}else{//当前待删除节点在中部的情况,删除位于中间节点ptTempNode->ptPrevNode->ptNextNode=ptTempNode->ptNextNode;//当前待删除节点在中部的情况,删除位于中间节点ptTempNode->ptNextNode->ptPrevNode=ptTempNode->ptPrevNode;}}//删除动态创建的链表项delete ptTempNode;//统计信息st_NodeDeleteNum++;iNodeAmount--;//返回待删除节点内容的实例指针return ptTempInstance;}//指针移动到下一个节点进行循环ptTempNode=ptTempNode->ptNextNode;}}}return NULL;}//显示所有子节点概要信息template<typename Type>void NodeList<Type>::ShowNodeList(HWND hWnd,HDC hDC,float x,float y,float h)
{int line=0;char szTemp[1024]="";sprintf(szTemp,"Show All Node In List[amount:%d]",iNodeAmount);glWindowPos2f(x,y-h*line++);drawString(hDC,szTemp);//显示头部和尾部指针信息sprintf(szTemp,"[head:%d][tail:%d]",ptNodeHead,ptNodeTail);glWindowPos2f(x,y-h*line++);drawString(hDC,szTemp);//标记首次开始循环的指针Node<Type> *ptTempNode=ptNodeHead;//记录序号int index=0;//遍历所有子物体进行判断while(ptTempNode!=NULL){ //定位显示位置glWindowPos2f(x,y-h*line++);//显示子物体信息sprintf(szTemp,"[%d][prev:%d][curr:%d][next:%d][inst:%d];",++index,ptTempNode->ptPrevNode,ptTempNode,ptTempNode->ptNextNode,ptTempNode->ptInstance);drawString(hDC,szTemp);ptTempNode=ptTempNode->ptNextNode;}}
5、在Object中添加对子列表的操作
我们刚才完成了链表模板,它的功能只是一个工具,应该只包括一些基本的链表操作。具体的添加子物体和删除子物体等操作不应该在列表本身实现,这些操作应该是抽象的Object类中来实现的。因此我们在Object类中添加了对子类的各种常用基础操作。同时,为了确保防范内存溢出,我们使用了一些统计变量来记录new和delete的使用情况。
//用于统计所有创建的物体个数,基类为Object实例的总创建和总销毁数量int st_ObjectCreateNum=0;int st_ObjectDeleteNum=0;//用于统计所有创建的物体个数,(静态创建)基类为Object实例的总创建和总销毁数量int st_ObjectStaticCreateNum=0;int st_ObjectStaticDeleteNum=0;//用于统计所有创建的物体个数,(动态创建)基类为Object实例的总创建和总销毁数量int st_ObjectDynamicCreateNum=0;int st_ObjectDynamicDeleteNum=0;//用于统计所有创建的物体个数,显示调试信息float ShowObjectStatistic(HWND hWnd,HDC hDC,float x,float y,float h=20);//重置统计数据void ResetObjectStatistic();//基础类class Object
{public://当前物体的名称char szName[100];//当前物体的类型char szType[100];//标记该物体是否为动态创建bool tagDynamicCreate;public://创建物体Object(); //标记为动态创建状态void SetDynamicCreateStatus();//析构函数添加为虚函数,才能确保所有继承类注销时调用virtual ~Object();//设置物体的名称void SetName(char *szTempName);//设置物体的名称void SetType(char *szTempType);public://当前物体的父物体指针Object *ptParent;//当前物体的子物体链表,用于存储所有子物体指针NodeList<Object> ChildNodeList;//添加某个子物体Object *AddChild(Object *ptTempObject);//删除某个子物体,并自动是否动态创建的子物体Object *DelChild(Object *ptTempObject);//删除所有子物体,并自动是否动态创建的子物体void DelAllChild();//非递归显示显示所有子节点内容,仅显示当前物体的子物体void ShowChildNodeList(HWND hWnd,HDC hDC,float x,float y,float h=20);//递归显示所有子节点信息,返回显示所有子节点后的光标位置,参数iChildLevel表示统计层级,参数iChildIndex子物体的统计编号float ShowAllChildNodeList(HWND hWnd,HDC hDC,float x,float y,int iChildLevel=0,int iChildIndex=0,float h=20);public://用于除构造函数以外的操作virtual void Initialize();//用于除析构函数以外的操作virtual void UnInitialize();public:virtual void OnSolid3DPaint(HWND hWnd,HDC hDC,Shader &tempShader);virtual void OnAlpha3DPaint(HWND hWnd,HDC hDC,Shader &tempShader);virtual void OnSolid2DPaint(HWND hWnd,HDC hDC,Shader &tempShader);virtual void OnTimer(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnMouseMove(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnLButtonDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnLButtonDblClk(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnLButtonUp(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnRButtonDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnRButtonDblClk(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnRButtonUp(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnMouseWheel(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnKeyDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnKeyUp(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnChar(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnImeComposition(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnSize(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnSetFocus(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);virtual void OnKillFocus(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);};
具体Object类的详细操作如下:
float ShowObjectStatistic(HWND hWnd,HDC hDC,float x,float y,float h)
{int line=0;//显示坐标信息文字glColor3f(1,0,0);//显示子物体链表char szTemp[1024]="";//显示统计信息glWindowPos2f(x,y-h*line++);sprintf(szTemp,"st_ObjectCreateNum:%d",st_ObjectCreateNum);drawString(hDC,szTemp);//显示统计信息glWindowPos2f(x,y-h*line++);sprintf(szTemp,"st_ObjectDeleteNum:%d",st_ObjectDeleteNum);drawString(hDC,szTemp);//显示统计信息glWindowPos2f(x,y-h*line++);sprintf(szTemp,"st_ObjectStaticCreateNum:%d",st_ObjectStaticCreateNum);drawString(hDC,szTemp);//显示统计信息glWindowPos2f(x,y-h*line++);sprintf(szTemp,"st_ObjectStaticDeleteNum:%d",st_ObjectStaticDeleteNum);drawString(hDC,szTemp);//显示统计信息glWindowPos2f(x,y-h*line++);sprintf(szTemp,"st_ObjectDynamicCreateNum:%d",st_ObjectDynamicCreateNum);drawString(hDC,szTemp);//显示统计信息glWindowPos2f(x,y-h*line++);sprintf(szTemp,"st_ObjectDynamicDeleteNum:%d",st_ObjectDynamicDeleteNum);drawString(hDC,szTemp);//返回显示后的纵坐标return y-h*line;}//重置统计数据void ResetObjectStatistic()
{//用于统计所有创建的物体个数,基类为Object实例的总创建和总销毁数量st_ObjectCreateNum=0;st_ObjectDeleteNum=0;//用于统计所有创建的物体个数,(静态创建)基类为Object实例的总创建和总销毁数量st_ObjectStaticCreateNum=0;st_ObjectStaticDeleteNum=0;//用于统计所有创建的物体个数,(动态创建)基类为Object实例的总创建和总销毁数量st_ObjectDynamicCreateNum=0;st_ObjectDynamicDeleteNum=0;}Object::Object()
{//当前物体的名称SetName("Object");//当前物体的类型SetType("Object");//当前物体的父物体指针ptParent=NULL;//统计信息st_ObjectCreateNum++;//标记是否动态创建tagDynamicCreate=false;//是否动态创建统计信息tagDynamicCreate==false?st_ObjectStaticCreateNum++:st_ObjectDynamicCreateNum++;}//标记为动态创建状态void Object::SetDynamicCreateStatus()
{tagDynamicCreate=true;//动态创建统计信息st_ObjectStaticCreateNum--;//动态创建统计信息st_ObjectDynamicCreateNum++;}Object::~Object()
{//递归删除所有的子节点及子物体DelAllChild();//统计信息st_ObjectDeleteNum++;//统计信息tagDynamicCreate==false?st_ObjectStaticDeleteNum++:st_ObjectDynamicDeleteNum++;}//设置物体的名称void Object::SetName(char *szTempName)
{strcpy(szName,szTempName);}//设置物体的名称void Object::SetType(char *szTempType)
{strcpy(szType,szTempType);}//添加某个子物体Object *Object::AddChild(Object *ptTempObject)
{if(ptTempObject!=NULL){//插入前先删除旧的子指针,防止出现重复添加两次的情况ChildNodeList.DelNode(ptTempObject);//在链表尾部添加一个节点,并保存指定的实例指针ChildNodeList.AddNode(ptTempObject);//在子物体中保存父物体的指针ptTempObject->ptParent=this;}//返回待添加的实例指针return ptTempObject;}//删除某个子物体Object *Object::DelChild(Object *ptTempObject)
{if(ptTempObject!=NULL){//删除链表中保存指定实例指针的子节点ChildNodeList.DelNode(ptTempObject);//在子物体中保存父物体的指针ptTempObject->ptParent=NULL;//释放动态创建的实例if(ptTempObject->tagDynamicCreate==true){delete ptTempObject;}}//返回待删除的实例指针return ptTempObject;}//删除所有子物体void Object::DelAllChild()
{//遍历链表项,删除指定内容的节点while(ChildNodeList.ptNodeTail!=NULL){//删除链表中的尾部的子节点,并返回子节点中保存的实例指针Object *ptTempObject=ChildNodeList.DelNode();//已删除节点返回的实例不能为空if(ptTempObject!=NULL){//在子物体中保存父物体的指针ptTempObject->ptParent=NULL;//释放动态创建的实例if(ptTempObject->tagDynamicCreate==true){delete ptTempObject;}}}}//非递归显示显示所有子节点内容void Object::ShowChildNodeList(HWND hWnd,HDC hDC,float x,float y,float h)
{//设置显示文字颜色glColor3f(1,0,0);//显示当前物体信息char szTemp[1024]="";glWindowPos2f(x,y);sprintf(szTemp,"%s[%d]",szName,this);drawString(hDC,szTemp);sprintf(szTemp,"ptParent:%s[%d]",(ptParent==NULL?"NULL":ptParent->szName),ptParent);drawString(hDC,szTemp);//调试信息,非递归显示链表调试信息ChildNodeList.ShowNodeList(hWnd,hDC,x,y-h);}//递归显示所有子节点信息,返回显示所有子节点后的光标位置float Object::ShowAllChildNodeList(HWND hWnd,HDC hDC,float x,float y,int iChildLevel,int iChildIndex,float h)
{//设置显示文字颜色glColor3f(1,0,0);//显示当前物体信息char szTemp[1024]="";glWindowPos2f(x+iChildLevel*20,y);sprintf(szTemp,"[%d][%d]->%s[%d][%s]",iChildLevel,iChildIndex,szName,ChildNodeList.iNodeAmount,tagDynamicCreate?"D":"S");drawString(hDC,szTemp);//设置首个子节点计数iChildIndex=0;//获取首个节点Node<Object> *ptTempNode=ChildNodeList.ptNodeHead;//循环显示各个子节点信息while(ptTempNode!=NULL){if(ptTempNode->ptInstance!=NULL){y=ptTempNode->ptInstance->ShowAllChildNodeList(hWnd,hDC,x,y-20,1+iChildLevel,iChildIndex++,h);}//进行下一个节点ptTempNode=ptTempNode->ptNextNode;}//返回光标的位置return y;}void Object::Initialize()
{}void Object::UnInitialize()
{}void Object::OnSolid3DPaint(HWND hWnd,HDC hDC,Shader &tempShader)
{}void Object::OnAlpha3DPaint(HWND hWnd,HDC hDC,Shader &tempShader)
{}void Object::OnSolid2DPaint(HWND hWnd,HDC hDC,Shader &tempShader)
{}void Object::OnTimer(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnMouseMove(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnLButtonDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnLButtonDblClk(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnLButtonUp(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnRButtonDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnRButtonDblClk(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnRButtonUp(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnMouseWheel(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnKeyDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnKeyUp(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnChar(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnImeComposition(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnSize(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnSetFocus(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}void Object::OnKillFocus(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{}
6、父子物体的操作及展示
在添加完以上的操作功能后,我们就可以方便的去创建父子物体的关系。同时为了方便查看,我们实现了一个递归函数的目录树显示功能,来用树形目录的形式展示父子物体的关系。我们可以通过任何一个父物体的ShowAllChildNodeList函数来展示实例树及其内容。
具体的树形目录显示如下,具体来说一个静态创建的MainSence为根节点,之后所有的游戏元素都会以子物体的方式添加到这个主要场景节点上。就是说我们主要场景中包括了一个坐标轴子物体,一个用于显示游戏帧的子物体,一个用于显示木箱的子物体(当然这个木箱这个子物体还可以拥有它的子物体),和一个全局的平行光子物体。将这些子物体添加到MainSence场景中后,我们就可以方便的统一在屏幕中显示到我们场景中所有的游戏元素。
有没有发现这个有点像unity里的结构展示窗口。
6.1、子物体的添加
所有Object类(以及Object类的派生类)均具有AddChild方法,可以使用AddChild函数添加两个物体的父子关系。
//自定义一个父物体和一个子物体Object ParentObject;//自定义一个子物体Object ChildObject1;ParentObject.AddChild(&ChildObject1);ChildObject1.SetName("ChildObject1");//创建动态实例,并添加到父物体子链表中Object *ptObject=new Object();ParentObject.AddChild(ptObject);ptObject->SetDynamicCreateStatus();ptObject->SetName("ChildObject2");
6.2、指定子物体的删除
在已经建立父子关系后,父物体可以通过DelChild函数和DelAllChild函数删除子物体。
//删除静态创建的子物体实例ParentObject.DelChild(&ChildObject1);//删除动态创建的子物体实例ParentObject.DelChild(ptObject);//删除所有子物体实例ParentObject.DelAllChild();
6.3、所有子物体的遍历显示
我们可以通过链表的循环,遍历各个子物体,并执行相应子物体的消息处理函数。
//获取首个子节点物体Node<Object> *ptTempNode=ParentObject.ChildNodeList.ptNodeHead;//循环执行各个子节点物体的消息处理函数while(ptTempNode!=NULL){if(ptTempNode->ptInstance!=NULL){ptTempNode->ptInstance->OnSolid3DPaint(hWnd,hDC,tempShader);}//进行下一个节点ptTempNode=ptTempNode->ptNextNode;}
7、用户互动添加父子物体例子(跳跃的立方体)
根据我们以上添加的父子结构功能,来实现一个小小的互动实例。我们这里有一个主场景类,初期这个类仅有一些必要的游戏元素(游戏帧、平行光),没有任何方块子物体,当用户点击鼠标右键时,通过消息处理函数自动添加一个动态生成的立方体,并将它添加到我们的主场景物体中,并开始不停的跳跃。因为我们已经在显示函数中添加了主场景(MainSence)内所有子物体的显示函数,主场景的所有子物体也会自动显示到我们的屏幕上。通过这种模式,我们极大的增加了我们的编程代码的便捷性。
7.1、添加Transform结构体
在做这个例子前,我们需要做一些准备工作。所有物体应该具有位置、旋转和缩放信息,所有我们需要一个Transform结构体,用于保存物体的位置、旋转和缩放信息。
//负责记录位置、旋转和缩放信息struct Transform
{//物体的位置、角度和缩放比例glm::vec3 Position;glm::vec3 Rotate;glm::vec3 Scale;//初始化Transform(){Position=glm::vec3(0.0f,0.0f,0.0f);Rotate=glm::vec3(0.0f,0.0f,0.0f);Scale=glm::vec3(1.0f,1.0f,1.0f);}};
有点类似于Unity界面操作控件中设置的要素。
7.2、添加GemeObject类
我们需要创建一个GemeObject类,它具有游戏所需具备的一些必要特性,比如说拥有Transform结构体,用于保存物体的位置、旋转和缩放信息。这个类时抽象出来的,主要是为了管理方便。
//游戏的基本类,具有游戏所需具备的一些必要特性class GameObject:public Object
{public://物体的位置,相对位置、相对角度和相对缩放比例Transform RelativeTransform;//物体的位置,考虑所有父物体后的绝对位置、绝对角度和绝对缩放比例Transform AbsoluteTransform;public:GameObject();};GameObject::GameObject()
{}
7.3、添加Cube立方体类
我们先创建一个立方体类,这个立方体只是显示一个预制的立方体,我们将在这个基本的立方体类基础上进一步生产“跳跃的立方体”。这里关于光照和材质的设置可以参照C++和OpenGL实现3D游戏编程【连载19】——着色器光照初步(平行光和光照贴图)中关于光照和材质的介绍。
//创建一个立方体预制体class Cube:public GameObject
{public://生成一个网格Mesh MainMesh;//颜色glm::vec4 Color;public:Cube();void OnSolid3DPaint(HWND hWnd,HDC hDC,Shader *tempShader);};Cube::Cube()
{//当前物体的名称SetName("Cube");SetType("Cube");//加载网格和纹理MainMesh.LoadMeshFromObjFile("Model\\Cube\\Cube.obj");}void Cube::OnSolid3DPaint(HWND hWnd,HDC hDC,Shader *tempShader)
{//重置并设置模型矩阵ModelMatrix=glm::mat4(1.0f);//根据位置、旋转和缩放信息设置模型矩阵ModelMatrix=glm::translate(ModelMatrix,RelativeTransform.Position);ModelMatrix=glm::rotate(ModelMatrix,RelativeTransform.Rotate.x,glm::vec3(1.0f,0.0f,0.0f));ModelMatrix=glm::rotate(ModelMatrix,RelativeTransform.Rotate.y,glm::vec3(0.0f,1.0f,0.0f));ModelMatrix=glm::rotate(ModelMatrix,RelativeTransform.Rotate.z,glm::vec3(0.0f,0.0f,1.0f));ModelMatrix=glm::scale(ModelMatrix,RelativeTransform.Scale);//启用光照light[0].tagEnable=1;light[1].tagEnable=0;//设置材质,启用颜色material.tagEnable=1;material.diffuse=Color;material.specular=Color;//启用着色器并进行渲染tempShader->UseShader();//显示网格MainMesh.DrawMesh(hWnd,hDC,tempShader);}
7.4、添加JumpingCube类
我们将在基础的立方体基础上,添加立方体的跳跃功能,这是我们最终需要的“跳跃的立方体”。
//创建一个跳跃立方体类class JumpingCube:public Cube
{public://记录调整时间float JumpStepTime;public:JumpingCube();void OnSolid3DPaint(HWND hWnd,HDC hDC,Shader *tempShader);};JumpingCube::JumpingCube()
{//当前物体的名称SetName("JumpingCube");SetType("JumpingCube");//跳跃调整时间JumpStepTime=0;//随机颜色switch(rand()%7){case 0:Color=glm::vec4(1.0f,1.0f,1.0f,1.0f);break;case 1:Color=glm::vec4(1.0f,0.0f,0.0f,1.0f);break;case 2:Color=glm::vec4(0.0f,1.0f,0.0f,1.0f);break;case 3:Color=glm::vec4(0.0f,0.0f,1.0f,1.0f);break;case 4:Color=glm::vec4(1.0f,1.0f,0.0f,1.0f);break;case 5:Color=glm::vec4(1.0f,0.0f,1.0f,1.0f);break;case 6:Color=glm::vec4(0.0f,1.0f,1.0f,1.0f);break;}}void JumpingCube::OnSolid3DPaint(HWND hWnd,HDC hDC,Shader *tempShader)
{//调整时间JumpStepTime+=0.05;//重置并设置模型矩阵ModelMatrix=glm::mat4(1.0f);//根据位置、旋转和缩放信息设置模型矩阵ModelMatrix=glm::translate(ModelMatrix,RelativeTransform.Position+glm::vec3(0.0f,Lerp(0.0f,3.0f,sin(JumpStepTime)),0.0f));//ModelMatrix=glm::rotate(ModelMatrix,RelativeTransform.Rotate.x,glm::vec3(1.0f,0.0f,0.0f));ModelMatrix=glm::rotate(ModelMatrix,RelativeTransform.Rotate.y+Lerp(0.0f,2*PI,JumpStepTime*0.2),glm::vec3(0.0f,1.0f,0.0f));//ModelMatrix=glm::rotate(ModelMatrix,RelativeTransform.Rotate.z,glm::vec3(0.0f,0.0f,1.0f));ModelMatrix=glm::scale(ModelMatrix,RelativeTransform.Scale+glm::vec3(Lerp(0.0f,0.5f,cos(JumpStepTime)/2+0.5),Lerp(0.0f,0.5f,cos(JumpStepTime)/2+0.5),Lerp(0.0f,0.5f,cos(JumpStepTime)/2+0.5)));//启用光照light[0].tagEnable=1;light[1].tagEnable=0;//设置材质,启用颜色material.tagEnable=1;material.diffuse=Color;material.specular=Color;//启用着色器并进行渲染tempShader->UseShader();//显示网格MainMesh.DrawMesh(hWnd,hDC,tempShader);}
7.5、鼠标右键添加跳跃的立方体
我们需要通过鼠标右键添加一些用户的互动操作,当用户点击鼠标右键时,会在随机位置产生于一个跳动的立方体,颜色随机,位置随机,立方体可以不断地跳动并进行旋转。同时添加跳动的立方体为主场景MainSence下的子物体,MainSence是一个GameObject类,他没有实际意义,仅仅作为一个父物体,将所有场景内游戏元素“挂”成该物体的子物体,即可实现统一管理。
case WM_RBUTTONDOWN:if(true){//新动态创建一个物体,并把该物体添加到主场景的子物体列表中JumpingCube *ptTempObject=new JumpingCube();if(ptTempObject!=NULL){//标记为动态创建,后期删除子物体时可自动删除释放内存ptTempObject->SetDynamicCreateStatus();//添加为子物体MyMainSence.AddChild(ptTempObject);//设置新创建物体的位移、旋转、缩放信息ptTempObject->RelativeTransform.Position=glm::vec3(10.0f-(rand()%20)*1.0f,0.0f,3.0f-(rand()%20)*0.3f);ptTempObject->RelativeTransform.Rotate=glm::vec3(PI*(rand()%360)/360.0f,PI*(rand()%360)/360.0f,PI*(rand()%360)/360.0f);ptTempObject->RelativeTransform.Scale=glm::vec3(0.5f,0.5f,0.5f);}}return 0;
同时我们需要显示我们物体,可以循环显示MainSence下的所有子物体。
case WM_PAINT:PAINTSTRUCT PS; hDC=BeginPaint(hWnd,&PS);//显示三维世界内容......//获取首个子节点物体Node<Object> *ptTempNode=MyMainSence.ChildNodeList.ptNodeHead;//循环执行各个子节点物体的消息处理函数while(ptTempNode!=NULL){if(ptTempNode->ptInstance!=NULL){ptTempNode->ptInstance->OnSolid3DPaint(hWnd,hDC,tempShader);}//进行下一个节点ptTempNode=ptTempNode->ptNextNode;}...... ReleaseDC(hWnd,hDC); return 0;
7.6、显示效果
最终的显示效果如下:
跳跃的方块
8、总结
C++作为一个下接操作系统硬件底层,上接用户逻辑的编程语言,为了适应各种环境,不为你不需要的东西付代价,C++是并没有提供原生内存管理GC的。STL库的那些智能指针更多只是在C++的语言层面上再提供一些小辅助。在最开始设计游戏引擎的时候,你不光要考虑该引擎所面对的用户群体和针对的游戏重点,更要开始考虑你所能利用到的都有什么内存管理方式。