96SEO 2026-02-19 20:39 17
上节课我们已经创建了一个基础Object类#xff0c;以后所有的游戏元素都可以从这个基类中派生出来。

同时为了操作方便#xff0c;我们可以为任意两个Object类#xff08;及其…欢迎来到zhooyu的专栏。
上节课我们已经创建了一个基础Object类以后所有的游戏元素都可以从这个基类中派生出来。
同时为了操作方便我们可以为任意两个Object类及其派生类的实例对象添加一种父子关系后期通过父物体与子物体关系能够轻松的实现游戏物体的操控方便我们在游戏中对所有游戏元素的组织和管理。
再比如后期加父子关系后就可以同步控制父子物体在三维空间的位置、旋转和缩放操作。
上节课介绍了Object类我们知道所有可以由Object类派生出来这是类之间的继承与被继承的关系指的是类。
同时我们还可以将具体类的实例附加一种父子关系概念指的是具体的实例。
父子关系就是假设我们有两个Object实例B和实例A这两个实例对应的类都是Object类那么我们就可以指定A和B的父子关系那么我们可以指定A是父物体B是子物体也可以指定B是父体A是子物体。
也就是指定一个物体添加为另一个物体的子物体此时我们称两个实例建立了父子关系。
Object是我们游戏里边绝大部分类的根类。
父物体通过一个链表可以保存他所有的子物体但每一个子物体只能有一个父物体这样就构造出了一个简单的树状结构。
我们可以通过链表的方式物体添加、删除、遍历的它的物体。
像许多游戏里头一样有些场景它是有一个MainSence根节点。
其他所有的游戏元素都是连接到这个游戏MainSence的子节点上去当然也可以是子节点的子节点以此类推。
就是说后期我们如果想给游戏中添加任何元素就只用给这个这个MainSence实例添加子节点就可以了。
我们游戏中Object对象之间可以以对象树的形式组织起来的。
子对象就会加入到父对象的一个成员变量叫Child孩子的List列表中我们程序中每个Object类都有一个ChildNodeList列表用于存储当前物体的所有子类实例同时每个子类实例都有一个Object类型的ptParent指针用于存储唯一的父物体指针。
当然如果当前物体不存在父物体实例时这个父指针也可以为空。
当父对象析构的时候这个列表ChildNodeList中的所有子对象也会被析构。
注意这里是说父对象和子对象不要理解成父类和子类。
我们今后所有创建的Object类或继承类的实例也可以也继承了这种对象树关系。
一个孩子可以添加成为父组件的一个子组件。
我们向某个Object对象实例中添加了Object对象实例建立父子关系当我们删除父子关系时我们的DelChild函数或DelAllChild默认情况下会将子对象实例析构同时我们子物体在析构过程中子物体的子物体以及子子子物体均会一同被析构确保内存能及时释放避免内存溢出问题。
这个结果也是我们开发人员所期望的。
比如如果有一个窗口和窗口中的一个按钮我们建立父子关系后窗口可以添加按钮为子物体。
后期当我们用窗口的DelChild函数删除和按钮的父子关系时其所在的窗口会自动将该按钮从其子对象列表ChildNodeList中删除并析构这个按键释放内存按钮在屏幕上消失。
当这个窗口析构的时候窗口ChildNodeList列表里边其他所有的子物体也会自动释放内存。
引入对象树的概念在一定程度上解决了内存问题。
因为我们设置了负物体与子物体的从属关系。
我们可以方便的控制他们之间的Transform操作。
后期我们添加相应功能后当父体移动时子物体也会随之移动。
但是反过来子物体移动时副物体不需要移动。
这种模式在旋转和缩放中同样适用。
这个场景其实比较常用。
比如说我们比如说我们去超市推了一个购物车装上一些商品后购物车和商品就构成了简单的父子关系。
购物车当做父物体车里的商品当成是子物体。
那么我们推着购物车移动时所有的子物体商品会随着我们的购物车父物体移动或旋转。
但是如果你仅仅摆动购物车里的商品时购物车是不会随之移动的。
日常生活中遍及着各种这样的父子关系模式比如汽车拉货物电梯和电梯里乘坐的人的关系书包和书包中的书的关系。
要实现对象树概念我们就必须有一个链表来实现功能。
当然我们可以直接使用STL中的List容器功能基本一致。
但为了我们后期操作的灵活性和代码清晰度我们自己准备了一个链表我们这里实现的是不带头双向不循环列表。
我们这个ChildNodeList链表中存储的是类实例的指针也就是说这个链表只负责保存父子关系。
具体的实物创建需要单独创建这个对象实物可以是静态定义的也可以是动态创建的。
不管是动态创建还是静态定义的我们都可以将它的指针通过AddChild函数添加到父物体的子链表中让他们之间形成父子从属关系。
为了操作方便我们使用了不带头双向不循环列表。
但同时还有一个问题由于我们Object拥有一个子链表链表中的保存的指针又是Object类型的因此我们必须使用C模板template来定义Node和NodeList类并创建这个ChildNodeList链表否则会出现前后创建逻辑顺序错误。
采用双向链方式表主要是为了查询、操作方便。
这里我们知道STL有现成的链表但是我们没有使用给自己写一个链表主要是为了能够更加自主的去操控链表。
大家如果为了方便也可以使用STL有现成的链表不过它是一个“带头双向循环链表”。
同时我们还在可以在后期扩展链表的使用方法方便我们的操作。
st_NodeCreateNum0;//用于统计所有创建的物体个数动态删除节点个数int
st_NodeDeleteNum0;//用于统计所有创建的物体个数显示调试信息float
ResetNodeStatistic();//自定义节点模板templatetypename
*ptInstance;//初始化结构体Node(){ptNextNodeNULL;ptPrevNodeNULL;ptInstanceNULL;}};//自定义双向链表模板templatetypename
iNodeAmount;public://构造函数NodeList();//从链表尾部添加一个保存特定实例指针的子节点Type
*ptTempInstance);//从链表尾部删除一个保存特定实例指针的子节点Type
*DelNode();//从链表中删除特定实例指针的一个子节点Type
*ptTempInstance);//非递归显示显示所有子节点概要信息void
line0;//显示坐标信息文字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
{//用于统计所有创建的物体个数动态创建节点个数st_NodeCreateNum0;//用于统计所有创建的物体个数动态删除节点个数st_NodeDeleteNum0;}//构造函数templatetypename
{ptNodeHeadNULL;ptNodeTailNULL;iNodeAmount0;}//从链表头部添加一个保存特定实例指针的子节点templatetypename
{//待添加实例指针不能为空if(ptTempInstance!NULL){//动态创建链表项NodeType
NodeType;//保存实例指针到节点中ptTempNode-ptInstanceptTempInstance;//将动态创建的链表节点添加到链表中if(ptNodeHeadNULL){//插入链表节点如果链表为空则直接添加ptNodeHeadptTempNode;ptNodeTailptTempNode;}else{//如果链表不为空则将动态创建的节点保存到链表的尾部ptTempNode-ptPrevNodeptNodeTail;//将子类保存到新创建的链表项指针中ptNodeTail-ptNextNodeptTempNode;//更新链表尾部指针ptNodeTailptTempNode;}//统计信息st_NodeCreateNum;//统计子节点个数iNodeAmount;}//返回节点内容的实例指针return
ptTempInstance;}//从链表尾部删除一个保存特定实例指针的子节点templatetypename
{//链表节点不能为空if(ptNodeTail!NULL){//从尾部删除动态节点NodeType
*ptTempNodeptNodeTail;//保存待删除节点内容的实例指针Type
*ptTempInstanceptNodeTail-ptInstance;//从尾部删除节点if(ptNodeTail-ptPrevNodeNULL){//重置尾部节点指针ptNodeHeadNULL;ptNodeTailNULL;}else{//删除尾部子节点指针ptNodeTail-ptPrevNode-ptNextNodeNULL;//设置新的尾部节点指针ptNodeTailptNodeTail-ptPrevNode;}//从尾部删除动态节点delete
ptTempNode;//统计信息st_NodeDeleteNum;//统计子节点个数iNodeAmount--;//返回待删除节点内容的实例指针return
NULL;}//从链表中删除特定实例指针的一个子节点templatetypename
{//待删除实例指针不能为空if(ptTempInstance!NULL){//链表不能为空if(ptNodeHead!NULL){//头部节点作为循环的开始节点NodeType
*ptTempNodeptNodeHead;//遍历链表项删除指定内容的节点while(ptTempNode!NULL){if(ptTempNode-ptInstanceptTempInstance){//当前待删除节点在头部的情况if(ptTempNodeptNodeHead){//判断是否只有一个节点if(ptTempNode-ptNextNodeNULL){//如果只有一项待删除则置空头部和尾部指针ptNodeHeadNULL;ptNodeTailNULL;}else{//如果有两项及以上节点将头部指针指向下一个节点ptNodeHeadptTempNode-ptNextNode;//将当前头节点的上一项指针重置为空ptNodeHead-ptPrevNodeNULL;}}else{//当前待删除节点不在头部的情况if(ptTempNodeptNodeTail){//当前待删除节点在尾部的情况尾部指针指向待删除节点的前一个节点ptNodeTailptTempNode-ptPrevNode;//当前待删除节点在尾部的情况将末尾的节点下一项指针置空删除掉ptNodeTail-ptNextNodeNULL;}else{//当前待删除节点在中部的情况删除位于中间节点ptTempNode-ptPrevNode-ptNextNodeptTempNode-ptNextNode;//当前待删除节点在中部的情况删除位于中间节点ptTempNode-ptNextNode-ptPrevNodeptTempNode-ptPrevNode;}}//删除动态创建的链表项delete
ptTempNode;//统计信息st_NodeDeleteNum;iNodeAmount--;//返回待删除节点内容的实例指针return
ptTempInstance;}//指针移动到下一个节点进行循环ptTempNodeptTempNode-ptNextNode;}}}return
NULL;}//显示所有子节点概要信息templatetypename
NodeListType::ShowNodeList(HWND
szTemp[1024];sprintf(szTemp,Show
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);//标记首次开始循环的指针NodeType
*ptTempNodeptNodeHead;//记录序号int
index0;//遍历所有子物体进行判断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);ptTempNodeptTempNode-ptNextNode;}}
我们刚才完成了链表模板它的功能只是一个工具应该只包括一些基本的链表操作。
具体的添加子物体和删除子物体等操作不应该在列表本身实现这些操作应该是抽象的Object类中来实现的。
因此我们在Object类中添加了对子类的各种常用基础操作。
同时为了确保防范内存溢出我们使用了一些统计变量来记录new和delete的使用情况。
//用于统计所有创建的物体个数基类为Object实例的总创建和总销毁数量int
st_ObjectDeleteNum0;//用于统计所有创建的物体个数静态创建基类为Object实例的总创建和总销毁数量int
st_ObjectStaticDeleteNum0;//用于统计所有创建的物体个数动态创建基类为Object实例的总创建和总销毁数量int
st_ObjectDynamicDeleteNum0;//用于统计所有创建的物体个数显示调试信息float
ResetObjectStatistic();//基础类class
tagDynamicCreate;public://创建物体Object();
SetDynamicCreateStatus();//析构函数添加为虚函数才能确保所有继承类注销时调用virtual
*szTempType);public://当前物体的父物体指针Object
*ptParent;//当前物体的子物体链表用于存储所有子物体指针NodeListObject
*ptTempObject);//删除某个子物体并自动是否动态创建的子物体Object
*ptTempObject);//删除所有子物体并自动是否动态创建的子物体void
DelAllChild();//非递归显示显示所有子节点内容仅显示当前物体的子物体void
h20);//递归显示所有子节点信息返回显示所有子节点后的光标位置参数iChildLevel表示统计层级参数iChildIndex子物体的统计编号float
h20);public://用于除构造函数以外的操作virtual
Initialize();//用于除析构函数以外的操作virtual
line0;//显示坐标信息文字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
{//用于统计所有创建的物体个数基类为Object实例的总创建和总销毁数量st_ObjectCreateNum0;st_ObjectDeleteNum0;//用于统计所有创建的物体个数静态创建基类为Object实例的总创建和总销毁数量st_ObjectStaticCreateNum0;st_ObjectStaticDeleteNum0;//用于统计所有创建的物体个数动态创建基类为Object实例的总创建和总销毁数量st_ObjectDynamicCreateNum0;st_ObjectDynamicDeleteNum0;}Object::Object()
{//当前物体的名称SetName(Object);//当前物体的类型SetType(Object);//当前物体的父物体指针ptParentNULL;//统计信息st_ObjectCreateNum;//标记是否动态创建tagDynamicCreatefalse;//是否动态创建统计信息tagDynamicCreatefalse?st_ObjectStaticCreateNum:st_ObjectDynamicCreateNum;}//标记为动态创建状态void
Object::SetDynamicCreateStatus()
{tagDynamicCreatetrue;//动态创建统计信息st_ObjectStaticCreateNum--;//动态创建统计信息st_ObjectDynamicCreateNum;}Object::~Object()
{//递归删除所有的子节点及子物体DelAllChild();//统计信息st_ObjectDeleteNum;//统计信息tagDynamicCreatefalse?st_ObjectStaticDeleteNum:st_ObjectDynamicDeleteNum;}//设置物体的名称void
{strcpy(szName,szTempName);}//设置物体的名称void
{strcpy(szType,szTempType);}//添加某个子物体Object
{if(ptTempObject!NULL){//插入前先删除旧的子指针防止出现重复添加两次的情况ChildNodeList.DelNode(ptTempObject);//在链表尾部添加一个节点并保存指定的实例指针ChildNodeList.AddNode(ptTempObject);//在子物体中保存父物体的指针ptTempObject-ptParentthis;}//返回待添加的实例指针return
{if(ptTempObject!NULL){//删除链表中保存指定实例指针的子节点ChildNodeList.DelNode(ptTempObject);//在子物体中保存父物体的指针ptTempObject-ptParentNULL;//释放动态创建的实例if(ptTempObject-tagDynamicCreatetrue){delete
ptTempObject;}}//返回待删除的实例指针return
{//遍历链表项删除指定内容的节点while(ChildNodeList.ptNodeTail!NULL){//删除链表中的尾部的子节点并返回子节点中保存的实例指针Object
*ptTempObjectChildNodeList.DelNode();//已删除节点返回的实例不能为空if(ptTempObject!NULL){//在子物体中保存父物体的指针ptTempObject-ptParentNULL;//释放动态创建的实例if(ptTempObject-tagDynamicCreatetrue){delete
ptTempObject;}}}}//非递归显示显示所有子节点内容void
{//设置显示文字颜色glColor3f(1,0,0);//显示当前物体信息char
szTemp[1024];glWindowPos2f(x,y);sprintf(szTemp,%s[%d],szName,this);drawString(hDC,szTemp);sprintf(szTemp,ptParent:%s[%d],(ptParentNULL?NULL:ptParent-szName),ptParent);drawString(hDC,szTemp);//调试信息非递归显示链表调试信息ChildNodeList.ShowNodeList(hWnd,hDC,x,y-h);}//递归显示所有子节点信息返回显示所有子节点后的光标位置float
Object::ShowAllChildNodeList(HWND
{//设置显示文字颜色glColor3f(1,0,0);//显示当前物体信息char
szTemp[1024];glWindowPos2f(xiChildLevel*20,y);sprintf(szTemp,[%d][%d]-%s[%d][%s],iChildLevel,iChildIndex,szName,ChildNodeList.iNodeAmount,tagDynamicCreate?D:S);drawString(hDC,szTemp);//设置首个子节点计数iChildIndex0;//获取首个节点NodeObject
*ptTempNodeChildNodeList.ptNodeHead;//循环显示各个子节点信息while(ptTempNode!NULL){if(ptTempNode-ptInstance!NULL){yptTempNode-ptInstance-ShowAllChildNodeList(hWnd,hDC,x,y-20,1iChildLevel,iChildIndex,h);}//进行下一个节点ptTempNodeptTempNode-ptNextNode;}//返回光标的位置return
在添加完以上的操作功能后我们就可以方便的去创建父子物体的关系。
同时为了方便查看我们实现了一个递归函数的的形式展示父子物体的关系。
我们可以通过任何一个父物体的ShowAllChildNodeList函数来展示实例树及其内容。
具体的树形目录显示如下具体来说一个静态创建的MainSence为根节点之后所有的游戏元素都会以子物体的方式添加到这个主要场景节点上。
就是说我们主要场景中包括了一个坐标轴子物体一个用于显示游戏帧的子物体一个用于显示木箱的子物体当然这个木箱这个子物体还可以拥有它的子物体和一个全局的平行光子物体。
将这些子物体添加到MainSence场景中后我们就可以方便的统一在屏幕中显示到我们场景中所有的游戏元素。
所有Object类以及Object类的派生类均具有AddChild方法可以使用AddChild函数添加两个物体的父子关系。
ChildObject1;ParentObject.AddChild(ChildObject1);ChildObject1.SetName(ChildObject1);//创建动态实例并添加到父物体子链表中Object
Object();ParentObject.AddChild(ptObject);ptObject-SetDynamicCreateStatus();ptObject-SetName(ChildObject2);6.2、指定子物体的删除
在已经建立父子关系后父物体可以通过DelChild函数和DelAllChild函数删除子物体。
//删除静态创建的子物体实例ParentObject.DelChild(ChildObject1);//删除动态创建的子物体实例ParentObject.DelChild(ptObject);//删除所有子物体实例ParentObject.DelAllChild();6.3、所有子物体的遍历显示
我们可以通过链表的循环遍历各个子物体并执行相应子物体的消息处理函数。
*ptTempNodeParentObject.ChildNodeList.ptNodeHead;//循环执行各个子节点物体的消息处理函数while(ptTempNode!NULL){if(ptTempNode-ptInstance!NULL){ptTempNode-ptInstance-OnSolid3DPaint(hWnd,hDC,tempShader);}//进行下一个节点ptTempNodeptTempNode-ptNextNode;}7、用户互动添加父子物体例子跳跃的立方体
根据我们以上添加的父子结构功能来实现一个小小的互动实例。
我们这里有一个主场景类初期这个类仅有一些必要的游戏元素游戏帧、平行光没有任何方块子物体当用户点击鼠标右键时通过消息处理函数自动添加一个动态生成的立方体并将它添加到我们的主场景物体中并开始不停的跳跃。
因为我们已经在显示函数中添加了主场景MainSence内所有子物体的显示函数主场景的所有子物体也会自动显示到我们的屏幕上。
通过这种模式我们极大的增加了我们的编程代码的便捷性。
在做这个例子前我们需要做一些准备工作。
所有物体应该具有位置、旋转和缩放信息所有我们需要一个Transform结构体用于保存物体的位置、旋转和缩放信息。
Scale;//初始化Transform(){Positionglm::vec3(0.0f,0.0f,0.0f);Rotateglm::vec3(0.0f,0.0f,0.0f);Scaleglm::vec3(1.0f,1.0f,1.0f);}};有点类似于Unity界面操作控件中设置的要素。
我们需要创建一个GemeObject类它具有游戏所需具备的一些必要特性比如说拥有Transform结构体用于保存物体的位置、旋转和缩放信息。
这个类时抽象出来的主要是为了管理方便。
{public://物体的位置相对位置、相对角度和相对缩放比例Transform
RelativeTransform;//物体的位置考虑所有父物体后的绝对位置、绝对角度和绝对缩放比例Transform
AbsoluteTransform;public:GameObject();};GameObject::GameObject()
我们先创建一个立方体类这个立方体只是显示一个预制的立方体我们将在这个基本的立方体类基础上进一步生产“跳跃的立方体”。
这里关于光照和材质的设置可以参照C和OpenGL实现3D游戏编程【连载19】——着色器光照初步平行光和光照贴图中关于光照和材质的介绍。
{//当前物体的名称SetName(Cube);SetType(Cube);//加载网格和纹理MainMesh.LoadMeshFromObjFile(Model\\Cube\\Cube.obj);}void
{//重置并设置模型矩阵ModelMatrixglm::mat4(1.0f);//根据位置、旋转和缩放信息设置模型矩阵ModelMatrixglm::translate(ModelMatrix,RelativeTransform.Position);ModelMatrixglm::rotate(ModelMatrix,RelativeTransform.Rotate.x,glm::vec3(1.0f,0.0f,0.0f));ModelMatrixglm::rotate(ModelMatrix,RelativeTransform.Rotate.y,glm::vec3(0.0f,1.0f,0.0f));ModelMatrixglm::rotate(ModelMatrix,RelativeTransform.Rotate.z,glm::vec3(0.0f,0.0f,1.0f));ModelMatrixglm::scale(ModelMatrix,RelativeTransform.Scale);//启用光照light[0].tagEnable1;light[1].tagEnable0;//设置材质启用颜色material.tagEnable1;material.diffuseColor;material.specularColor;//启用着色器并进行渲染tempShader-UseShader();//显示网格MainMesh.DrawMesh(hWnd,hDC,tempShader);}7.4、添加JumpingCube类
我们将在基础的立方体基础上添加立方体的跳跃功能这是我们最终需要的“跳跃的立方体”。
JumpStepTime;public:JumpingCube();void
*tempShader);};JumpingCube::JumpingCube()
{//当前物体的名称SetName(JumpingCube);SetType(JumpingCube);//跳跃调整时间JumpStepTime0;//随机颜色switch(rand()%7){case
0:Colorglm::vec4(1.0f,1.0f,1.0f,1.0f);break;case
1:Colorglm::vec4(1.0f,0.0f,0.0f,1.0f);break;case
2:Colorglm::vec4(0.0f,1.0f,0.0f,1.0f);break;case
3:Colorglm::vec4(0.0f,0.0f,1.0f,1.0f);break;case
4:Colorglm::vec4(1.0f,1.0f,0.0f,1.0f);break;case
5:Colorglm::vec4(1.0f,0.0f,1.0f,1.0f);break;case
6:Colorglm::vec4(0.0f,1.0f,1.0f,1.0f);break;}}void
JumpingCube::OnSolid3DPaint(HWND
{//调整时间JumpStepTime0.05;//重置并设置模型矩阵ModelMatrixglm::mat4(1.0f);//根据位置、旋转和缩放信息设置模型矩阵ModelMatrixglm::translate(ModelMatrix,RelativeTransform.Positionglm::vec3(0.0f,Lerp(0.0f,3.0f,sin(JumpStepTime)),0.0f));//ModelMatrixglm::rotate(ModelMatrix,RelativeTransform.Rotate.x,glm::vec3(1.0f,0.0f,0.0f));ModelMatrixglm::rotate(ModelMatrix,RelativeTransform.Rotate.yLerp(0.0f,2*PI,JumpStepTime*0.2),glm::vec3(0.0f,1.0f,0.0f));//ModelMatrixglm::rotate(ModelMatrix,RelativeTransform.Rotate.z,glm::vec3(0.0f,0.0f,1.0f));ModelMatrixglm::scale(ModelMatrix,RelativeTransform.Scaleglm::vec3(Lerp(0.0f,0.5f,cos(JumpStepTime)/20.5),Lerp(0.0f,0.5f,cos(JumpStepTime)/20.5),Lerp(0.0f,0.5f,cos(JumpStepTime)/20.5)));//启用光照light[0].tagEnable1;light[1].tagEnable0;//设置材质启用颜色material.tagEnable1;material.diffuseColor;material.specularColor;//启用着色器并进行渲染tempShader-UseShader();//显示网格MainMesh.DrawMesh(hWnd,hDC,tempShader);}7.5、鼠标右键添加跳跃的立方体
我们需要通过鼠标右键添加一些用户的互动操作当用户点击鼠标右键时会在随机位置产生于一个跳动的立方体颜色随机位置随机立方体可以不断地跳动并进行旋转。
同时添加跳动的立方体为主场景MainSence下的子物体MainSence是一个GameObject类他没有实际意义仅仅作为一个父物体将所有场景内游戏元素“挂”成该物体的子物体即可实现统一管理。
WM_RBUTTONDOWN:if(true){//新动态创建一个物体并把该物体添加到主场景的子物体列表中JumpingCube
JumpingCube();if(ptTempObject!NULL){//标记为动态创建后期删除子物体时可自动删除释放内存ptTempObject-SetDynamicCreateStatus();//添加为子物体MyMainSence.AddChild(ptTempObject);//设置新创建物体的位移、旋转、缩放信息ptTempObject-RelativeTransform.Positionglm::vec3(10.0f-(rand()%20)*1.0f,0.0f,3.0f-(rand()%20)*0.3f);ptTempObject-RelativeTransform.Rotateglm::vec3(PI*(rand()%360)/360.0f,PI*(rand()%360)/360.0f,PI*(rand()%360)/360.0f);ptTempObject-RelativeTransform.Scaleglm::vec3(0.5f,0.5f,0.5f);}}return
0;同时我们需要显示我们物体可以循环显示MainSence下的所有子物体。
hDCBeginPaint(hWnd,PS);//显示三维世界内容......//获取首个子节点物体NodeObject
*ptTempNodeMyMainSence.ChildNodeList.ptNodeHead;//循环执行各个子节点物体的消息处理函数while(ptTempNode!NULL){if(ptTempNode-ptInstance!NULL){ptTempNode-ptInstance-OnSolid3DPaint(hWnd,hDC,tempShader);}//进行下一个节点ptTempNodeptTempNode-ptNextNode;}......
C作为一个下接操作系统硬件底层上接用户逻辑的编程语言为了适应各种环境不为你不需要的东西付代价C是并没有提供原生内存管理GC的。
STL库的那些智能指针更多只是在C的语言层面上再提供一些小辅助。
在最开始设计游戏引擎的时候你不光要考虑该引擎所面对的用户群体和针对的游戏重点更要开始考虑你所能利用到的都有什么内存管理方式。
【上一节】C和OpenGL实现3D游戏编程【连载20】——游戏基类Object的构建
【下一节】[C和OpenGL实现3D游戏编程【连载22】——更新中
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback