当前位置: 首页 > news >正文

CPT205 计算机图形学 OpenGL 3D实践(CW2)

文章目录

  • 1. 介绍
  • 2. 设计
  • 3. 准备阶段
  • 4. 角色构建
  • 5. 场景构建
  • 6. 交互部分
    • 6.1 键盘交互
    • 6.2 鼠标交互
    • 6.3 鼠标点击出多级菜单进行交互
  • 7. 缺点与问题
    • 7.1 程序bug
    • 7.2 游戏乐趣不足
    • 7.3 画面不够好看
  • 8. 完整代码

1. 介绍

前面已经分享过了关于CPT205的CW1的2D作业,这次CW2要求的是3D作业,但是主题不限,不像上次规定了必须得是生日贺卡。这里将以这个大作业为例,讲解OpenGL的3D实践。
首先虽然我们来到了3D作业,但我们能靠OpenGL做一个类似于我们现在所处的世界一样的作业吗?答案当然是不能。虽然现在的游戏或者说计算机图形学已经可以让我们能做出接近现实世界的虚拟世界,但是那需要非常细致的建模和设计。很明显不是作业要求的。我们使用的工具较为简单,我们需要考虑的主要是以下几个问题:1. 建模建成什么样的?2. 怎么给建好的模型上色?3D不像2D那样直接在建模的时候就给物体赋予了颜色,3D的颜色最后要靠灯光和材质等最后呈现。当然我们有贴图,贴图也可以让我们的模型呈现出我们想要的颜色或者样子。3. 我们建好的这些模型在一起是什么主题?虽然作业要求中没有给出我们的主题,但是我不能这里摆几个方形的房子,那里摆几个方形的车,这里摆几个火柴人,在一起完全不是一个主题的在一起。或者说由于它们是一个主题的,所以它们有相同的画风,因此它们在一起给人以一种和谐的感觉,人们才会愿意沉浸在这个虚拟的世界中,如果我们搭建的场景里的不同对象有不同的画风,那组合在一起用户就很容易与这个虚拟世界脱离。
相较于一般做作业的时候我们先想好主题再围绕主题开展,这次我更推荐先去思考我能做出或者找到什么贴图,然后是我该怎么用贴图。因为比如我想做一个类似真实世界的贴图,我拍了很多现实世界的照片作为贴图,很明显我们只有主题和贴图,其余的部分不是我们所能完成的。但是我们如果找到一系列符合某个主题的贴图,或者说我们把一个画风的贴图在一起我们就找到了我们的主题。这便是我想说的这次作业和上次作业的一大不同,只有这样才能做出漂亮优秀的作业。当然这次作业和上次作业一样,你要用到之前学习的知识,相关的知识可以参考这几个链接CPT205计算机图形学Pt.2
CPT205计算机图形学Pt.3
CPT205计算机图形学Pt.4
下面我将和前面一样,分享自己的作业以及想法,并且说出我的问题,希望能帮助到大家。

2. 设计

其实刚开始我想做一个博物馆出来,然后博物馆里面全都是画作,用户可以控制自己的角色再里面移动,观看这些画作。但是问题主要是我当时想的是做卢浮宫,卢浮宫是由玻璃组成,想做出一个逼真的玻璃很明显不能依赖于贴图,因为贴图是静态的,不会因为外部环境的变化而出现变化。我感觉在这里freeglut库中没有能让这里某个物体拥有类似玻璃的那种反光效果的材质的方法。在尝试很多种方法后,我放弃了。
当然你也可以不选择做一个卢浮宫出来,你可以做一个正常的哥特式建筑风格的博物馆,里面摆满画,然后玻璃采用哥特式建筑的彩色玻璃,你或许在想这里的贴图怎么办。我这里推荐一个找资源的好渠道——Quixel BridgeQuixel Bridge官网,在这个上面你可以找到免费的高清资源,其中就有我前面说的需要的资源。但这个网站是全英文的,如果遇到问题还可以查看以下b站上的一些视频教程,然后搜索资源的时候也记得使用英文去搜索。
当然当我前面的弯路走完后我感觉自己已经没有足够的时间去建造一个哥特式建筑了,因为我想做成有外景和内景的博物馆。当然如果有外景的画我们还需要做一个skybox(天空盒),也就是我们建造的这个虚拟世界是一个正方体,活动空间在这个正方体之内。用户在正方体内,所以正方体内部的六个面就是这个世界的地、天与四周。其实我们如果在网上搜索skybox资源网上也会有6张图组成的skybox资源以供使用,当然也可以选择自己去做6张图组成的skybox。所以如果做的就是博物馆的内景的画就可以避开这一步,当然也有一些方式去避开,比如规定用户无法看到头上的天空,即固定视角等。
下面就是我的正式作业,我回归到了之前上一次的卡比主题,因为模拟真实的灯光效果之类的太难了,我还是决定回到卡通的这种赛道上,而且我认为卡比相关的建模和贴图都比较简单。上次用户是用户,卡比是卡比,这次我们可以让卡比作为故事的主角,用户自己操作卡比,让卡比像一个正常角色一样去移动。如果我们玩过一些游戏就知道,要是角色移动的话就会有腿部和手上的动作,甚至角色身上的衣服也会动,但是卡比可以坐着星星,也就是说卡比像开飞船一样开着星星移动,这样就避开了要做一些动画的问题。我们将卡比和星星绑定在一起就可以实现卡比的移动,但是只是让他们进行wasd的移动是不够的,我们虽然没开过星星也没开过飞船,但是我们看过别人骑马,当骑马人想让马停下就会让马前半身抬起来,当骑马人想冲刺,就会压低自己的身姿。我们可以让刚刚卡比和星星的整体在某个方向上旋转从而模拟出这种物理效果,从而提高画面的真实感。
向前的侧视图如下图所示。
在这里插入图片描述
向后的侧视图如下图所示。
在这里插入图片描述
这里我设计的是ad负责控制卡比转向,因为显示中没人可以斜着走路,都是往前或者往后走,因此我是这样转向的设计。
我们现在规定卡比可以移动,那做什么呢?如果我们想进一步提升这里的交互性,那只移动可不够,控制的角色应该能做出更多的事情,绝不仅仅是移动那么简单。我想的是可以和前面CW1的主题联系在一起。CW1中卡比是去给用户庆祝生日的,我们现在可以做成卡比还可以攻击,为什么要攻击,攻击是为了保护一个东西——给朋友生日准备的礼物。这样我们就有了一个主题。卡比该怎么攻击能,不像别的一些角色的攻击,我们前一篇文章说过,卡比可以吞噬物体,然后变身获取对应物体的能力,但在普通状态下,卡比也可以吞噬物体,然后将其变成星星发射,所以卡比的攻击我们可以做成星星从卡比嘴里发出,从而攻击。因此我们的这个程序的基本玩法也确立了下来,站在星星上的卡比,努力攻击尝试拿走或者破坏生日礼物的敌人。敌人被卡比的星星击中就会死亡,当然怪物也可以攻击卡比,卡比的生命有限,要是一直被攻击,那卡比也会晕过去,无法及时参加朋友的生日,所以失败。当然怪物要是碰到生日礼物,游戏也会结束,因为生日礼物被破坏了。因此前面的转向设计也方便了这里的攻击操作。
所以我们就确定了它的整个主题,我这里想做成类似于RPG(角色扮演游戏)的操作和视角变化,即用户可以控制角色移动的同时还可以控制摄像机与角色的相对位置,可以方便用户在控制角色攻击的同时观察不同地方的敌人,或者控制摄像机以让用户到自己习惯的角度进行操作。所以我这样的设计是很好的,我给用户更多的选择机会,从而提高了交互性。当然你也可以做成固定视角,比如第一视角,但是这样就没法展现出你做的角色模型。
所以我们就基本确定了整个我们作业的内容了,玩家操作卡比保护生日礼物免遭周围的怪物的破坏,同时玩家也要让卡比免遭怪兽的攻击。

3. 准备阶段

准备阶段依然像前文一样,围绕贴图来准备,所以首先是贴图方法。注意贴图需要的图片是bmp格式,我使用电脑上的画图软件将图片转换成bmp格式导出从而得到的bmp格式。

//This function is reading the image for the texture.
void ReadImage(const char path[256], GLint& imagewidth, GLint& imageheight, GLint& pixellength)
{GLubyte* pixeldata;FILE* pfile;fopen_s(&pfile, path, "rb");if (pfile == 0) exit(0);fseek(pfile, 0x0012, SEEK_SET);fread(&imagewidth, sizeof(imagewidth), 1, pfile);fread(&imageheight, sizeof(imageheight), 1, pfile);pixellength = imagewidth * 3;while (pixellength % 4 != 0) pixellength++;pixellength *= imageheight;pixeldata = (GLubyte*)malloc(pixellength);if (pixeldata == 0) exit(0);fseek(pfile, 54, SEEK_SET);fread(pixeldata, pixellength, 1, pfile);p.push_back(pixeldata);fclose(pfile);
}//This function is used for some initializations. Such as enabling depth testing, texture and lighting.
//This function call "ReadImage" for the textures and set the texture objects.
//It also set the light.
void myinit(void) {glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LEQUAL);glShadeModel(GL_SMOOTH);glEnable(GL_TEXTURE_2D);ReadImage("kirbyFace.bmp", imageWidth[0], imageHeight[0], pixelLength[0]);ReadImage("kirbyFoot.bmp", imageWidth[1], imageHeight[1], pixelLength[1]);ReadImage("kirbyHand.bmp", imageWidth[2], imageHeight[2], pixelLength[2]);ReadImage("waddleFace.bmp", imageWidth[3], imageHeight[3], pixelLength[3]);ReadImage("waddleFoot.bmp", imageWidth[4], imageHeight[4], pixelLength[4]);ReadImage("waddleHand.bmp", imageWidth[5], imageHeight[5], pixelLength[5]);ReadImage("star.bmp", imageWidth[6], imageHeight[6], pixelLength[6]);ReadImage("water.bmp", imageWidth[7], imageHeight[7], pixelLength[7]);ReadImage("leftSky.bmp", imageWidth[8], imageHeight[8], pixelLength[8]);ReadImage("frontSky.bmp", imageWidth[9], imageHeight[9], pixelLength[9]);ReadImage("rightSky.bmp", imageWidth[10], imageHeight[10], pixelLength[10]);ReadImage("backSky.bmp", imageWidth[11], imageHeight[11], pixelLength[11]);ReadImage("topSky.bmp", imageWidth[12], imageHeight[12], pixelLength[12]);ReadImage("box.bmp", imageWidth[13], imageHeight[13], pixelLength[13]);ReadImage("specialBox.bmp", imageWidth[14], imageHeight[14], pixelLength[14]);glPixelStorei(GL_UNPACK_ALIGNMENT, 1);glGenTextures(textureNumber, &texture[0]);for (int i = 0; i < 15; i++){glBindTexture(GL_TEXTURE_2D, texture[i]);glTexImage2D(GL_TEXTURE_2D, 0, 3, imageWidth[i], imageHeight[i], 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, p[i]);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);}glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);GLfloat mat_shininess[] = { 50.0 };glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);GLfloat light_position[] = { light_x, light_y, light_z, 0.0 };glLightfv(GL_LIGHT0, GL_POSITION, light_position);glEnable(GL_LIGHTING);glEnable(GL_LIGHT0);
}

贴图的方法在Lab中提及,先由ReadImage方法将贴图的图片读取起来,然后在myinit方法中将读取的图片制作成对应的纹理,然后在后面我们需要对应的纹理时,我们就可以直接使用,从而完成贴图。
前面我们在设计中说过这里的背景故事,以及游戏里的故事。那么如果让用户或者玩家了解这里的故事呢?你可以直接写字告诉它们,但这样很明显索然无味,不是好的解决方案。你可以用一些设计的方式潜在地告诉用户这里的故事。所以刚出场我们可以看到卡比,卡比身下会有这个礼物,我将其做成马里奥里的问号方块,这让用户有想打开的想法,但是我并没有教怎么操作。那这里的操作最好是符合人们的习惯,因此wasd操作,人们不知道怎么操作的时候就会用他们熟悉的操作方式。然后用户会发现它们可以控制卡比移动,所以他们自己就是这个卡比,但是怎么移动都无法靠近碰触到这个宝箱,所以这个宝箱不是打开的。然后用户可能会去别的地方寻找答案,他们发现这个程序的名字是Kirby Defends Gift(卡比保卫礼物),所以他们可能就会想:“哦!这个应该就是卡比要保卫的礼物。”用户此时可能发现视野里会出现一些怪物靠近这边,如果用户移动了一定的距离,就会发现那些怪物的目标是礼物,所以那些就成了这个故事里的怪物。当然这里还会有一些插曲,比如用户怎么知道是wasd移动而不是键盘上的方向键移动,方向键也是用户很容易想到的操作角色移动的方式,但那其实被我设置成了控制摄像头移动的方式,它们也能靠这里的尝试知道这样的方式,这给了用户探索的兴趣。包括鼠标我也设计了交互方式。
在这里插入图片描述
由于我们的作业需要使用上课学习到的知识,所以我制作了一个菜单以作为提示以演示这个程序如何交互,同时这个菜单还添加了额外功能。如下图所示。
在这里插入图片描述
菜单的详细情况将在第六节(交互部分)介绍。
当然在准备阶段的时候我想强调这里的游戏里的一些提示,我想到一款小时候很热门的游戏——植物大战僵尸,当玩家失败时,除了要进房子的僵尸,其余的画面都会静止,通过这样的方式,将画面聚焦于一个地方,从而告诉了玩家失败的原因。于是我准备做一个类似于2D里的过场动画,当游戏失败,画面会静止,通过一个动画让玩家聚焦于失败的地方,从而让游戏结束。当然我们无法直接使用上次2D作业里的代码,因为那是2D的,我们现在是3D的,那我们直接加一个z值就解决问题了吗?当然不是。但是我们的3D画面都会在摄像机的投影下变成2D画面呈现在屏幕上,所以我们先要想好摄像机的位置在哪里。我将其放在能清楚拍清楚失败原因的位置,然后将这个星星动画以一个与摄像机垂直的角度放置在距离摄像机一定距离的位置,这样我们便能呈现出和我们想法近似的效果。当然这个方法有问题,比如如果有别的东西出现在前面我们说的星星动画和摄像机之间,就会暴露出来,我尽力让这个概率降低了,但还是存在这样的问题,这是我计算不好导致的问题。相关的代码如下。

//This function is the animation for failure.
//When the game is over this function will be called and shows a star viewport for the failure reason
//It will be created outside of the viewport first and be shown when the game is over.
void scene()
{R = 10 - (10 * transitionTimer);glPushMatrix();glTranslatef(cameraPositionX + 0.0f, cameraPositionY - 0.4f, cameraPositionZ - 0.4f);glRotatef(-45.0f, 1.0f, 0.0f, 0.0f);for (int i = 0; i < 5; i++){glRotatef(72.0, 0.0, 0.0, 1.0);glBegin(GL_POLYGON);glVertex3f(0, R - sceneDisappear, 0);glVertex3f(0, 10 - sceneDisappear, 0);glVertex3f(-10, 10 - sceneDisappear, 0);glVertex3f(-10, R * cosf(72.0f * angleRadian) - sceneDisappear, 0);glVertex3f(-R * cosf(72.0f * angleRadian) / cosf(36.0f * angleRadian) * sinf(36.0f * angleRadian), R * cosf(72.0f * angleRadian) - sceneDisappear, 0);glEnd();}glPopMatrix();
}

我不知道为什么,在这里的实践中我发现OpenGL里的场景无法像我想象中的那样当场创建某一对象,所以我的想法是将这些东西提前创建好,然后拿到摄像机看不见的位置。当需要的时候,控制它们的位置,包括后面的敌人以及星星攻击我都是这么设计的。在这里我修复了上次说的建模上的计算问题,而是采用的使用OpenGL的Transformation方法从而快速创建了这里的动画。
现在我们说说后面的建模,在卡比的世界观里有一种敌人叫做瓦豆鲁迪,其实和卡比长得很像,我决定将其作为这里的敌人,因为它可以和卡比使用相同的方式做出来,而且足够可爱。在这门课的第8部分我们学习了层次建模,其实和上次的2D卡比类似,我们将这里的卡比就像层次建模里的例子一样处理,我们创建一个卡比的身体,然后根据卡比的身体对应卡比的四肢,然后我们在最后再加上乘坐的星星,我们就完成了卡比。当然瓦豆鲁迪也是这个方法。
下面的代码是卡比的相关组件。

//This function creates the body of Kirby.
//It will be called for creating Kirby.
void kirbyBody()
{glBindTexture(GL_TEXTURE_2D, texture[0]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);gluSphere(quadricObj, 0.16, 360, 360);gluDeleteQuadric(quadricObj);
}//This function creates the foot of Kirby.
//It will be called for creating Kirby.
void kirbyFoot()
{glBindTexture(GL_TEXTURE_2D, texture[1]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);glPushMatrix();glScalef(0.8f, 0.5f, 1.0f);gluSphere(quadricObj, 0.12, 360, 360);glPopMatrix();gluDeleteQuadric(quadricObj);
}//This function creates the hand of Kirby.
//It will be called for creating Kirby.
void kirbyHand()
{glBindTexture(GL_TEXTURE_2D, texture[2]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);glPushMatrix();glScalef(1.0, 0.6f, 0.6f);gluSphere(quadricObj, 0.08, 360, 360);glPopMatrix();gluDeleteQuadric(quadricObj);
}

有的人可能对这里的贴图有疑问,因为贴图感觉是一个方形贴方形,像卡比这样的球体该怎么处理呢,但其实Lab中的例子举的两个例子中的另一个就是球体。我们可以想象这里贴图就是一块贴画,然后我们沿着球体将这个贴画正好贴下去,想清楚这一点很关键,我将利用这一点制作后面立体的五角星。你可以自己多尝试几次。
相应的瓦豆鲁迪的代码如下,两者代码的区别在于使用了不同的贴图。

//This function creates the body of Waddle.
//It will be called for creating Waddle.
void waddleBody()
{glBindTexture(GL_TEXTURE_2D, texture[3]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);gluSphere(quadricObj, 0.16, 360, 360);gluDeleteQuadric(quadricObj);
}//This function creates the foot of Waddle.
//It will be called for creating Waddle.
void waddleFoot()
{glBindTexture(GL_TEXTURE_2D, texture[4]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);glPushMatrix();glScalef(0.8f, 0.5f, 1.0f);gluSphere(quadricObj, 0.12, 360, 360);glPopMatrix();gluDeleteQuadric(quadricObj);
}//This function creates the hand of Waddle.
//It will be called for creating Waddle.
void waddleHand()
{glBindTexture(GL_TEXTURE_2D, texture[5]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);glPushMatrix();glScalef(1.0, 0.6f, 0.6f);gluSphere(quadricObj, 0.08, 360, 360);glPopMatrix();gluDeleteQuadric(quadricObj);
}

这里不像前面2D作业先有一些基础图元的方法然后再是一些组件,然后是最后具体的对象。因为这里后面的建模很简单,所以我没有采用上次的三段式结构,当然如果你的设计比较复杂,觉得复用是必要的可以像我前一次作业一样作为三段结构呈现。

4. 角色构建

前面准备阶段我们已经将卡比和瓦豆鲁迪的组件已经准备好了,这时候我们将这些组件组装起来。
卡比的代码如下。

//This function uses hierarchical modeling to creat Kirby with the previous functions.
void kirby() {glPushMatrix();glTranslatef(kirbyPosition[0], kirbyPosition[1], kirbyPosition[2]);glRotatef(-kirbyAngle, 0.0, 1.0, 0.0);glRotatef(angleMoving, 1.0, 0.0, 0.0);glPushMatrix();glRotatef(-90.0, 1.0, 0.0, 0.0);kirbyBody();glPopMatrix();glPushMatrix();glTranslatef(0.16, 0.0, 0.0);kirbyHand();glTranslatef(-0.06, -0.16, 0.0);kirbyFoot();glPopMatrix();glPushMatrix();glTranslatef(-0.16, 0.0, 0.0);kirbyHand();glTranslatef(0.06, -0.16, 0.0);kirbyFoot();glPopMatrix();glPushMatrix();glTranslatef(0.0, -0.28, -0.06);star(0.36f, 0.1f);glPopMatrix();glPopMatrix();
}

卡比的特点是首先它本来出现在虚拟世界的中心,也是我们的画面中心。然后玩家操作后,这个位置的值是变化的,我使用了一个kirbyPosition的数组去储存这里的xyz的值。当然这里的y值其实不会变化,这里我是为了方便起见直接创立了一个大小为3的数组储存xyz的值。
而且卡比只有一个,对于瓦豆来说,你需要的是一个能生成多个瓦豆的方法,所以这个方法需要额外的参数记录瓦豆的位置,当然位置还不够。首先瓦豆的目标是拿到礼物,所以它会向礼物方向前进,因此它会直接面向礼物,所以我们需要旋转瓦豆从而让瓦豆面向礼物前进。因此这个方法还需要旋转角度的参数。当然这个角度是靠瓦豆的位置以及礼物的位置确定的,但是这个方法涉及到瓦豆的位置移动,所以如果这个角度不是作为参数传入,而是一直计算这就浪费了资源。我们完全可以在生成瓦豆的初始位置时,将这个角度值直接计算好然后作为定值传入。知道这个角度后,其实也可以使用Transformation直接将其在自己的坐标系中不断向前,使用glTranslatef即可。但我这里使用的是将具体的坐标位置利用angle计算出来。
具体的代码如下。

//This function uses hierarchical modeling to creat Waddle with the previous functions.
//Unlike the previous method for creating Kirby, it rotates for showing Waddle is moving.
//"x","y","z" defines the position of Waddle.
//"angle" keeps Waddle can face to the center.
void waddle(float x, float y, float z, float angle) {glPushMatrix();glTranslated(x, y, z);glRotatef(-angle, 0, 1, 0);glRotatef(20.0, 1.0, 0.0, 0.0);glPushMatrix();glRotatef(-90.0, 1.0, 0.0, 0.0);waddleBody();glPopMatrix();glPushMatrix();glTranslatef(0.16, 0.0, 0.0);waddleHand();glTranslatef(-0.06, -0.16, 0.0);waddleFoot();glPopMatrix();glPushMatrix();glTranslatef(-0.16, 0.0, 0.0);waddleHand();glTranslatef(0.06, -0.16, 0.0);waddleFoot();glPopMatrix();glPushMatrix();glTranslatef(0.0, -0.28, -0.06);star(0.36f, 0.1f);glPopMatrix();glPopMatrix();
}

就像前文所说,我发现无法现场创建和删除对象,我想的方案是将对象的位置进行瞬移。所以刚开始这些瓦豆也被拿到了摄像机无法看见的位置,然后需要的时候立马出现在场景中,不需要的时候立马将其移到场景之外。
因此关于卡比攻击所使用的星星也是这么处理的。但星星如果我们在建模时直接使用一个黄色的贴图去贴我认为会让五角星不够立体,所以我在贴图上使用的是提前将立体效果放进去的贴图。然后我用这样特殊的贴图去实现五角星的立体感觉。
贴图如下。
在这里插入图片描述

五角星是由五个角构成的,这个角可以拆分成两个三棱锥,其中两个面重合在一起,靠里的两个面不用展示,所以是四个三角形组成。三角形不是前面贴图中的那些矩形或者前面说的球形,但是其实和矩形是类似的,我们可以理解四边形是三个定点还有一个动点组成的,这三个定点正是三角形,这个四边形是由正常矩形拉伸得来的,所以我们将这里进行一个逆向,我们就可以得出需要的立体贴图是什么样的。这里需要你对贴图的原理有一定的了解,即它是坐标的对应,然后你可以想象一下这里转换的方程是什么样的,然后尝试自己尝试一下,具体的细节也可以参考我后面的资源分享。
星星的代码具体如下。

//This function creates a star.
//It will be called for Kirby, Waddle and the star bullet.
//"R" is the radius of the star,"height: is the thickness of the star.
void star(float R, float height)
{glBindTexture(GL_TEXTURE_2D, texture[6]);float r = 1.8 * R / (sinf(36.0f * angleRadian) * cosf(18.0f * angleRadian) / sinf(18.0f * angleRadian) + cosf(36.0f * angleRadian));glPushMatrix();for (int i = 0; i < 5; i++){glRotatef(72.0, 0.0, 1.0, 0.0);glBegin(GL_TRIANGLES);glTexCoord2f(0.0f, 0.0f); glVertex3f(0.0f, height, 0.0f);glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 0.0f, R);glTexCoord2f(1.0f, 1.0f); glVertex3f(r * sin(36.0f * angleRadian), 0.0f, r * cos(36.0f * angleRadian));glTexCoord2f(0.0f, 0.0f); glVertex3f(0.0f, height, 0.0f);glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 0.0f, R);glTexCoord2f(1.0f, 1.0f); glVertex3f(-r * sin(36.0f * angleRadian), 0.0f, r * cos(36.0f * angleRadian));glTexCoord2f(0.0f, 0.0f); glVertex3f(0.0f, -height, 0.0f);glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 0.0f, R);glTexCoord2f(1.0f, 1.0f); glVertex3f(r * sin(36.0f * angleRadian), 0.0f, r * cos(36.0f * angleRadian));glTexCoord2f(0.0f, 0.0f); glVertex3f(0.0f, -height, 0.0f);glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 0.0f, R);glTexCoord2f(1.0f, 1.0f); glVertex3f(-r * sin(36.0f * angleRadian), 0.0f, r * cos(36.0f * angleRadian));glEnd();}glPopMatrix();
}

然后这个星星被用于作为卡比和瓦豆脚下移动的工具,这个星星通过Transformation的旋转和缩放还可以作为卡比攻击时发射的星星使用。
代码如下所示。

//This function creates a star bullet.
void starBullet()
{glPushMatrix();glTranslatef(starBulletPosition[0], starBulletPosition[1], starBulletPosition[2]);glRotatef(-starBulletAngle, 0.0, 1.0, 0.0);glRotatef(-90.0, 1.0, 0, 0);star(0.18f, 0.05f);glPopMatrix();
}

当然这里星星子弹也是和前面处理方式一样,先放到场景外,需要的时候再到场景中出现。
无论是卡比被怪物攻击,还是怪物成功被攻击,这里最好有一个受到攻击的提示动画,我这里主要是需要想我这里应该是什么样的场景比较好呢。其实如果是在野外草地上的话,在传统的场景建造中,如果反复使用相同的资源就会让用户觉得这里的场景是假的,因此我想避免这样的情况,再加上我需要找一个符合卡比世界的周围的环境作为skybox(天空盒),这个四周的面最好自然有山之类的作为阻挡。根据这个我在卡比的游戏中截取了画面作为这个天空盒的一部分,这些场景中满足我刚刚的需求,然后由于这里的图案中有山有水,如下图所示。
在这里插入图片描述
所以我想着要不将这一切都放在海上,放在海上然后我随机加一些海上的东西,这样就避免了使用草地作为地面的重复感,而且可以让用户觉得因为是海上,所以卡比和瓦豆都需要在星星上移动。因此这里受到攻击的动画可以做一种类似于机毁人亡的感觉,我想到一些卡丁车游戏里道具进攻别人,会把别人弄得在原地晕头转向,无法操作一段时间。于是我决定当任何角色被攻击,角色就会原地旋转,无法控制。如果是卡比,那如果卡比血量还有,那么还可以继续游戏,如果卡比血量没有了,那游戏结束。对于瓦豆,如果被攻击那就是在动画结束后,自动消失,代表死亡。因为前面说的海上星星驾驶的设定,因此如果怪物碰到了卡比,双方可以理解为撞车在一起,双双原地眩晕转圈。
相关的代码其实很简单,就是利用OpenGL的Transformation让对象原地旋转即可。
代码如下所示。

void kirbyHurt()
{kirbyAngle += 5.0f;kirbyAnimationTimer += 0.2f;
}

问题的关键在于控制,这里需要一个flag控制卡比是否在眩晕的状态,如果在那么就运行上面的方法,同时这个值的变化导致后面交互部分里用户无法再控制卡比。
完成这一步后,我们回到瓦豆上,瓦豆就需要复杂一些,而且我后面的菜单功能加了额外功能,这个功能便是很多游戏里出现的“秘籍”,即可以直接操控游戏里的一些东西,其中我想了一些方法,简单的比如控制卡比血量,但是复杂的就有全屏秒杀之类的,对瓦豆的攻击方法。
前面我们有一个flag控制卡比是否在眩晕的状态,这里只需要一个布尔值就能控制,但是瓦豆的话我认为有三个状态——1. 死亡状态,该状态下瓦豆其实在场景之外。 2. 生存状态,该状态下,瓦豆正努力靠近礼物。 3. 被攻击了,正在动画中,可以理解成正在死亡的状态。因此我这里使用了一个int值来控制这三个状态。然后进行一系列的控制。比如检查所有的瓦豆中,哪些是生存状态,若不足上限,便让一个死亡状态的瓦豆转化为生存状态,即创造一个瓦豆敌人出来。再比如检查所有的生存下的瓦豆,将这些瓦豆全部进入到第三个状态,播放对应的动画,从而将这些瓦豆全部清楚。
关于具体的实现细节包括判定攻击的距离计算都可以看我后面第八节(完整代码)中的细节。

5. 场景构建

前面的几个部分其实已经将这里的场景构建说的差不多了。我们先做出这里的skybox(天空盒),代码如下。

//This function is used for making a skyBox for this game.
void skyBox()
{//WaterglBindTexture(GL_TEXTURE_2D, texture[7]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(-10.0, -0.9, 10.0);glTexCoord2f(0.0, 1.0); glVertex3f(-10.0, -0.9, -10.0);glTexCoord2f(1.0, 1.0); glVertex3f(10.0, -0.9, -10.0);glTexCoord2f(1.0, 0.0); glVertex3f(10.0, -0.9, 10.0);glEnd();//LeftSkyglBindTexture(GL_TEXTURE_2D, texture[8]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(-10.0, -2, 10.0);glTexCoord2f(0.0, 1.0); glVertex3f(-10.0, 18.0, 10.0);glTexCoord2f(1.0, 1.0); glVertex3f(-10.0, 18.0, -10.0);glTexCoord2f(1.0, 0.0); glVertex3f(-10.0, -2.0, -10.0);glEnd();//FrontSkyglBindTexture(GL_TEXTURE_2D, texture[9]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(-10.0, -2.0, -10.0);glTexCoord2f(0.0, 1.0); glVertex3f(-10.0, 18.0, -10.0);glTexCoord2f(1.0, 1.0); glVertex3f(10.0, 18.0, -10.0);glTexCoord2f(1.0, 0.0); glVertex3f(10.0, -2.0, -10.0);glEnd();//RightSkyglBindTexture(GL_TEXTURE_2D, texture[10]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(10.0, -2.0, -10.0);glTexCoord2f(0.0, 1.0); glVertex3f(10.0, 18.0, -10.0);glTexCoord2f(1.0, 1.0); glVertex3f(10.0, 18.0, 10.0);glTexCoord2f(1.0, 0.0); glVertex3f(10.0, -2.0, 10.0);glEnd();//BackSkyglBindTexture(GL_TEXTURE_2D, texture[11]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(10.0, -2.0, 10.0);glTexCoord2f(0.0, 1.0); glVertex3f(10.0, 18.0, 10.0);glTexCoord2f(1.0, 1.0); glVertex3f(-10.0, 18.0, 10.0);glTexCoord2f(1.0, 0.0); glVertex3f(-10.0, -2.0, 10.0);glEnd();//TopSkyglBindTexture(GL_TEXTURE_2D, texture[12]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(-10.0, 18.0, -10.0);glTexCoord2f(0.0, 1.0); glVertex3f(-10.0, 18.0, 10.0);glTexCoord2f(1.0, 1.0); glVertex3f(10.0, 18.0, 10.0);glTexCoord2f(1.0, 0.0); glVertex3f(10.0, 18.0, -10.0);glEnd();
}

你可以根据这里的注释将你准备的贴图进行替换。
回到刚刚说的这里的地面,或者说海面,我为了避免使用相同贴图的重复,我决定使用一张贴图作为海面,而这张贴图我随机添加了一些荷叶在上面,从而避免重复。如下图所示。
在这里插入图片描述
就这样我们完成了天空盒的创建。但是这样的场景还是有一些单调,我又想到了既然卡比是守护礼物,那可能周围也有一些其他的方块的箱子,但是卡比的这个不一样,所以瓦豆看到才会过来哄抢。这样我们建模简单,又可以添加一些不一样的东西在场景中。于是我又随机添加了一些箱子在场景中,为了体现这个地方是海面,所以有的方块高有的方块低,甚至我这里的高低会变化,但是我这里的变化值很小,非常不明显,这一点可以再进行一些改进。
相关的代码如下。

//It creats the box for decorating the game scene.
// "x","y","z" defines the postion of the box.
// "size" difines the half length of the box.
//It will be called in "boxScene".
void box(float x, float y, float z, float size)
{glBindTexture(GL_TEXTURE_2D, texture[13]);glBegin(GL_QUADS);//frontglTexCoord2f(0.0, 0.0); glVertex3f(x - size, y - size, z + size);glTexCoord2f(0.0, 1.0); glVertex3f(x - size, y + size, z + size);glTexCoord2f(1.0, 1.0); glVertex3f(x + size, y + size, z + size);glTexCoord2f(1.0, 0.0); glVertex3f(x + size, y - size, z + size);//backglTexCoord2f(0.0, 0.0); glVertex3f(x + size, y - size, z - size);glTexCoord2f(0.0, 1.0); glVertex3f(x + size, y + size, z - size);glTexCoord2f(1.0, 1.0); glVertex3f(x - size, y + size, z - size);glTexCoord2f(1.0, 0.0); glVertex3f(x - size, y - size, z - size);//leftglTexCoord2f(0.0, 0.0); glVertex3f(x + size, y - size, z + size);glTexCoord2f(0.0, 1.0); glVertex3f(x + size, y + size, z + size);glTexCoord2f(1.0, 1.0); glVertex3f(x + size, y + size, z - size);glTexCoord2f(1.0, 0.0); glVertex3f(x + size, y - size, z - size);//rightglTexCoord2f(0.0, 0.0); glVertex3f(x - size, y - size, z - size);glTexCoord2f(0.0, 1.0); glVertex3f(x - size, y + size, z - size);glTexCoord2f(1.0, 1.0); glVertex3f(x - size, y + size, z + size);glTexCoord2f(1.0, 0.0); glVertex3f(x - size, y - size, z + size);//topglTexCoord2f(0.0, 0.0); glVertex3f(x - size, y + size, z + size);glTexCoord2f(0.0, 1.0); glVertex3f(x - size, y + size, z - size);glTexCoord2f(1.0, 1.0); glVertex3f(x + size, y + size, z - size);glTexCoord2f(1.0, 0.0); glVertex3f(x + size, y + size, z + size);//bottomglTexCoord2f(0.0, 0.0); glVertex3f(x - size, y - size, z - size);glTexCoord2f(0.0, 1.0); glVertex3f(x - size, y - size, z + size);glTexCoord2f(1.0, 1.0); glVertex3f(x + size, y - size, z + size);glTexCoord2f(1.0, 0.0); glVertex3f(x + size, y - size, z - size);glEnd();
}//It creates some box with "box" functions to decorate the game scene.
//"y" will combines with the global variable "angleWave" to control the y value for the box to make the box up and down like function of the waves.
void boxScene(float y)
{box(3.0, 0.1 * cos(y) - 1.0, 7.0, 0.15);box(2.0, 0.2 * cos(y) - 1.0, 3.0, 0.15);box(-5.0, 0.2 * cos(y) - 1.0, -6.5, 0.15);box(-8.0, 0.1 * cos(y) - 1.0, 1.0, 0.15);glRotatef(30.0, 0.0, 1.0, 0.0);box(-1.0, 0.1 * cos(y) - 1.0, 7.0, 0.15);box(7.0, 0.2 * cos(y) - 1.0, -1.0, 0.15);box(4.0, 0.1 * cos(y) - 1.0, 6.0, 0.15);box(6.0, 0.2 * cos(y) - 1.0, -3.2, 0.15);glRotatef(15.0, 0.0, 1.0, 0.0);box(-1.4, 0.15 * cos(y) - 1.0, 2.0, 0.15);box(8.8, 0.25 * cos(y) - 1.0, -0.7, 0.15);box(0.9, 0.1 * cos(y) - 1.0, -6.0, 0.15);box(1.8, 0.2 * cos(y) - 1.0, -3.2, 0.15);box(-3.0, 0.15 * cos(y) - 1.0, 5.0, 0.15);box(-4.0, 0.25 * cos(y) - 1.0, 3.0, 0.15);box(-5.0, 0.1 * cos(y) - 1.0, 4.1, 0.15);box(5.5, 0.2 * cos(y) - 1.0, 2.9, 0.15);box(-2.7, 0.1 * cos(y) - 1.0, 1.1, 0.15);box(-1.8, 0.2 * cos(y) - 1.0, -3.4, 0.15);glRotatef(-45.0, 0.0, 1.0, 0.0);
}

由于这些箱子是一样的,所以我先写了一个创建箱子的方法,然后再随机添加一些坐标作为其出现的位置,并且方便后续模拟海浪的效果从而箱子上上下下移动。
同理我们很容易得到卡比要守护的礼物的方法,如下所示。

//It creates the defence target, which is the gift for friend's birthday.
//"y" controls its position.
//"size" difines the half length of the box.
void specialBox(float y, float size)
{glBindTexture(GL_TEXTURE_2D, texture[14]);glBegin(GL_QUADS);//frontglTexCoord2f(0.0, 0.0); glVertex3f(0 - size, y - size, 0 + size);glTexCoord2f(0.0, 1.0); glVertex3f(0 - size, y + size, 0 + size);glTexCoord2f(1.0, 1.0); glVertex3f(0 + size, y + size, 0 + size);glTexCoord2f(1.0, 0.0); glVertex3f(0 + size, y - size, 0 + size);//backglTexCoord2f(0.0, 0.0); glVertex3f(0 + size, y - size, 0 - size);glTexCoord2f(0.0, 1.0); glVertex3f(0 + size, y + size, 0 - size);glTexCoord2f(1.0, 1.0); glVertex3f(0 - size, y + size, 0 - size);glTexCoord2f(1.0, 0.0); glVertex3f(0 - size, y - size, 0 - size);//leftglTexCoord2f(0.0, 0.0); glVertex3f(0 + size, y - size, 0 + size);glTexCoord2f(0.0, 1.0); glVertex3f(0 + size, y + size, 0 + size);glTexCoord2f(1.0, 1.0); glVertex3f(0 + size, y + size, 0 - size);glTexCoord2f(1.0, 0.0); glVertex3f(0 + size, y - size, 0 - size);//rightglTexCoord2f(0.0, 0.0); glVertex3f(0 - size, y - size, 0 - size);glTexCoord2f(0.0, 1.0); glVertex3f(0 - size, y + size, 0 - size);glTexCoord2f(1.0, 1.0); glVertex3f(0 - size, y + size, 0 + size);glTexCoord2f(1.0, 0.0); glVertex3f(0 - size, y - size, 0 + size);//topglTexCoord2f(0.0, 0.0); glVertex3f(0 - size, y + size, 0 + size);glTexCoord2f(0.0, 1.0); glVertex3f(0 - size, y + size, 0 - size);glTexCoord2f(1.0, 1.0); glVertex3f(0 + size, y + size, 0 - size);glTexCoord2f(1.0, 0.0); glVertex3f(0 + size, y + size, 0 + size);//bottomglTexCoord2f(0.0, 0.0); glVertex3f(0 - size, y - size, 0 - size);glTexCoord2f(0.0, 1.0); glVertex3f(0 - size, y - size, 0 + size);glTexCoord2f(1.0, 1.0); glVertex3f(0 + size, y - size, 0 + size);glTexCoord2f(1.0, 0.0); glVertex3f(0 + size, y - size, 0 - size);glEnd();
}

我们就基本完成了整个程序。

6. 交互部分

交互部分,首先除了基础的操作以外,一些设计也提高了这里的交互感,比如卡比和瓦豆移动的时候的倾斜以及受到攻击时的动画这些都是设计细节上的交互感。这里着重说一些交互方法上的交互,不仅有基础的操作,还有交互菜单。

6.1 键盘交互

键盘交互除了基础的退出程序以外,还有如何操控卡比以及摄像机的移动,或者这里你可以根据自己的需求进行设计。我想做成wasd移动,方向键上下左右控制摄像机移动,e键作为经常使用到的交互键,这里是用来发射子弹。但是这里需要注意,摄像机这里应该是在空间中移动,或者理解成摄像机有自己的轨道以及镜头,轨道决定其的一个位置,它可能是沿着轨道绕着某个物体旋转(即我们不能让摄像机一直向上,从而让这个世界颠倒过来了)。镜头可以理解成广角我们就能看得很远,也可以是摄像头被拉远了。因此我们在设计时候要有6个关于摄像机的控制,并且对于前面我说的轨道的控制需要规定其移动的范围。其实卡比也要规定其移动的范围,否则玩家就会控制其离开这里创造的skybox(天空盒)中,也就是需要设计一个空气墙。
相关的代码如下。

//This function controls the interaction with special keys in the keyboard.
void specialKeys(int key, int x, int y)
{//The up arrow key can move the camera upwards.if (key == GLUT_KEY_UP && gaming == true && cameraVerticalAngle <= 89.0f * angleRadian){cameraVerticalAngle += 1.0f * angleRadian;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}//The down arrow key can move the camera downwardsif (key == GLUT_KEY_DOWN && gaming == true && cameraVerticalAngle >= -20.0f * angleRadian){cameraVerticalAngle -= 1.0f * angleRadian;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}//The left arrow key can make the camera turn leftif (key == GLUT_KEY_LEFT && gaming == true){cameraHorizontalAngle -= 1.0f * angleRadian;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}//The right arrow key can make the camera turn rightif (key == GLUT_KEY_RIGHT && gaming == true){cameraHorizontalAngle += 1.0f * angleRadian;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}glutPostRedisplay();
}//This function controls the interaction with normal keys in the keyboard.
void keyboardInput(unsigned char key, int x, int y)
{//Press 'W' or 'S' will make Kirby forward or backward.//Kirby will tilt towards the direction of motion to make it more like it is in motion.//Press 'A' or 'D' will make Kirby turn left or right.//Press 'E' or 'Space' to shoot the star bullet.//Press 'Esc' to quit the game.angleMoving = 0.0f;if (((key == 'w' || key == 'W') && kirbyNormal == true) && gaming == true){angleMoving = 20.0f;if ((std::abs(kirbyPosition[2] + 0.05 * cos(-kirbyAngle * angleRadian)) < 8.5) && (std::abs(kirbyPosition[0] + 0.05 * cos(-kirbyAngle * angleRadian)) < 8.5)){kirbyPosition[2] += 0.05 * cos(-kirbyAngle * angleRadian);kirbyPosition[0] += 0.05 * sin(-kirbyAngle * angleRadian);}}if (((key == 's' || key == 'S') && kirbyNormal == true) && gaming == true){angleMoving = -20.0f;if ((std::abs(kirbyPosition[2] - 0.05 * cos(-kirbyAngle * angleRadian)) < 8.5) && (std::abs(kirbyPosition[0] - 0.05 * cos(-kirbyAngle * angleRadian)) < 8.5)){kirbyPosition[2] -= 0.05 * cos(-kirbyAngle * angleRadian);kirbyPosition[0] -= 0.05 * sin(-kirbyAngle * angleRadian);}}if (((key == 'a' || key == 'A') && kirbyNormal == true) && gaming == true){kirbyAngle -= 1;}if (((key == 'd' || key == 'D') && kirbyNormal == true) && gaming == true){kirbyAngle += 1;}if ((((key == 'e' || key == 'E' || key == ' ') && starBulletMoving != true) && kirbyNormal == true) && gaming == true){starAttack();}if (key == 27){exit(0);}glutPostRedisplay();}

我这里将退出程序做成按esc键而非Q键,在specialKeys方法中我规定了摄像机移动的轨道,其的镜头我放在了鼠标交互中。

6.2 鼠标交互

前面说到用鼠标控制摄像机的镜头,我觉得这样比较自然,当我们鼠标滚轮向上滚动,我们一般都是放大,因此这里摄像机也会靠近,即放大了对象。同理,当我们鼠标滚轮向下滚动,我们就是缩小,摄像机远离对象,即缩小了对象。相关的代码如下。

//This function controls the interation with the mouse.
void mouse(int button, int state, int x, int y)
{//Sliding the roller upwards will enlarge.if (button == 3 && gaming == true && cameraDistance > 0.4f){cameraDistance -= 0.1f;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}//Sliding the roller downwards will shrink.else if (button == 4 && gaming == true && cameraDistance < 2.0f){cameraDistance += 0.1f;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}glutPostRedisplay();
}

这里只有鼠标的滚轮操作,因为右击呼出菜单时在创建菜单时,将创建的菜单附加到鼠标右键点击事件上。

6.3 鼠标点击出多级菜单进行交互

这里的代码使用其实不难,Lab中的代码拿过来进行一定量的修改即可,我将其做成多个二级菜单,其中第一个二级菜单提供类似于软件的帮助支持,告诉用户该如何操作。第二个二级菜单即我前面说的“秘籍”,这个菜单下面还有子菜单从而提供相应的支持。相关的代码如下。

//Following two functions provides the menu for the user.
//In the menu the user can get the help for how to play the game.
//In the menu the user can cheat with the cheat operations submenu for the cheating modes.
//In the menu the user can quit the game directly.
//This function will react the operation with the corresponding "num" value.
void menu(int num) {switch (num) {case 1:exit(0);break;case 2:killCheat();break;case 3:killAllCheat();break;case 4:if (kirbyLife < 3){lifeCheat(3 - kirbyLife);}break;case 5:lifeCheat(10);break;case 6:lifeCheat(100);break;case 7:lifeCheat(999);break;case 8:break;case 9:waddleMovingSpeedUp();break;case 10:waddleMovingSpeedDown();break;}
}//This function initials and creates the menus.
void initMenu() {int help = glutCreateMenu(menu);glutAddMenuEntry("w: Kirby go staight", 8);glutAddMenuEntry("s: Kirby go back", 8);glutAddMenuEntry("a: Kirby turn left", 8);glutAddMenuEntry("d: kirby turn right", 8);glutAddMenuEntry("Using direction keys controls the camera", 8);glutAddMenuEntry("e / space: Launch stars", 8);glutAddMenuEntry("esc: Quit the game", 8);int difficultyCheatOptions = glutCreateMenu(menu);glutAddMenuEntry("Difficulty up", 9);glutAddMenuEntry("Difficulty down", 10);int killCheatOptions = glutCreateMenu(menu);glutAddMenuEntry("Kill one", 2);glutAddMenuEntry("Kill all", 3);int lifeCheatOptions = glutCreateMenu(menu);glutAddMenuEntry("Life max", 4);glutAddMenuEntry("Life +10", 5);glutAddMenuEntry("Life +100", 6);glutAddMenuEntry("Life +999", 7);int cheatOptions = glutCreateMenu(menu);glutAddSubMenu("Kill cheat options:", killCheatOptions);glutAddSubMenu("Life cheat options:", lifeCheatOptions);glutAddSubMenu("Difficulty cheat options:", difficultyCheatOptions);int menuID = glutCreateMenu(menu);glutAddSubMenu("Operation instructions:", help);glutAddSubMenu("Cheat options:", cheatOptions);glutAddMenuEntry("Quit the game(Esc)", 1);glutAttachMenu(GLUT_RIGHT_BUTTON);
}

这里的initMenu方法下的glutAttachMenu(GLUT_RIGHT_BUTTON)将创建的菜单附加到鼠标右键点击事件上。所以不需要在mouse的方法上添加额外的操作。这里initMenu相当于创建对应的菜单和对应的事件编号,然后根据事件编号,前面的menu方法会将其跳转到对应的操作下。这部分其实不难。

7. 缺点与问题

7.1 程序bug

程序主要有以下两个bug——1. 星星结算动画可能会导致穿模,这个地方我前文说过是因为我的计算问题,其实如果设计的没问题,可以解决。
2.限制卡比移动时的方法有问题,以至于如果卡比装上空气墙。卡比可能无法移动。这里代码处理的不好,其实可以变成如果值增加就会超出边界,则这个值不变。

7.2 游戏乐趣不足

首先是这个背景故事量其实存在一定的联想量,而实际上游戏给的提示不够。而这里游戏的难度大,很容易无法给玩家带来乐趣,这会导致玩家不会有足够的耐心去思考游戏里的故事,有精力也更多的放在了游戏上,而非故事背景上,所以可能感受到的乐趣会进一步下降。即游戏带来的挫败感让游戏的乐趣下降了。这主要是我时间不足的原因,希望大家能花更多时间去制作自己的作业。

7.3 画面不够好看

相较于上次的2D作业,很明显这次的作业我觉得自己的画面不如上次那样游戏,我这里比如周围的场景可以再改进。以及这些贴图和建模也可以再精致一些,但由于时间问题,我都没有来得及去进一步修饰。

8. 完整代码

前面很多地方只是展示了最相关的代码,以及部分示例代码,没有展示这些代码中调用的一些方法,以及其他的代码。这里展示全部代码,希望能帮助到大家。当然如果想要直接获取这个程序的相关所有贴图以及程序,可以查看我的GitHub项目GitHub仓库。

//This coursework makes a small game about Kirby. 
//The story of this game can be viewed as a prequel to coursework1, Kirby encountered an accident on the way to the birthday party.
//In this game the user should control Kirby to denfence the box at the center which is the birthday gift for Kirby's friend.
//This game is a 3D game and it provides many operations for the users, which includes controlling Kirby and the camera, and some additional functions.
//Such as exit the game and cheating mode menu.
//When Kirby is out of health value or Waddle(the monster) get to the center, this game will be immovable and focus on the perspective of failure.
#define FREEGLUT_STATIC
#include <GL/freeglut.h>  
#include <math.h>
#include <cmath>
#include <iostream>
#include <random>
#include <chrono>
#include <stdio.h> 
#include <stdlib.h> 
#include "vector" using namespace std;//There are some constants pre defined in the project for future use.
const float angleRadian = 3.1415926 / 180.0f;
const int textureNumber = 16;//There are some variables for controlling the whole game.
//Some variables control the camera, some variables control Kirby, some variables control Waddles, some variables control the failure animation and the scene.
float cameraDistance = 0.8f;
float cameraHorizontalAngle = 0.0f;
float cameraVerticalAngle = 0.0f;
float lookatPositionX = 0.0f, lookatPositionY = 0.0f, lookatPositionZ = 0.0f;
float cameraPositionX = lookatPositionX + cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = lookatPositionY + cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = lookatPositionZ + cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);
float kirbyPosition[3] = { 0.0f, 0.0f, 0.0f };
float kirbyAngle = 0.0f;
int kirbyLife = 3;
float kirbyAnimationTimer = 0.0f;
bool kirbyNormal = true;
float starBulletPosition[3] = { 10.0f, -10.0f, 0.0f };
float starBulletAngle = 0.0f;
bool starBulletMoving = false;
bool moving = false;
int waddleNumber = 0;
float waddlePosition[4][3] = {{0.0f, -10.0f, 0.0f},{0.0f, -10.0f, 0.0f},{0.0f, -10.0f, 0.0f},{0.0f, -10.0f, 0.0f},
};
float waddleAngle[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
int waddleState[4] = { 0, 0, 0, 0 };
float waddleAnimationTimer[4] = { 0.0f, 0.0f , 0.0f, 0.0f };
float waddleMovingSpeed = 4.0f;
float timer = 0.0f;
bool gaming = true;
float transitionTimer = 0.0f;
float R = 10 - (10 * transitionTimer);
float sceneDisappear = 1000.0f;
float angleMoving = 0.0f;
float angleWave = 0.0f;//These are used for textures and lighting.
GLint imageWidth[textureNumber];
GLint imageHeight[textureNumber];
GLint pixelLength[textureNumber];
vector<GLubyte*>p;
GLuint texture[textureNumber];
GLfloat light_x = 0.0, light_y = 2.0, light_z = 0.0;//This function is reading the image for the texture.
void ReadImage(const char path[256], GLint& imagewidth, GLint& imageheight, GLint& pixellength)
{GLubyte* pixeldata;FILE* pfile;fopen_s(&pfile, path, "rb");if (pfile == 0) exit(0);fseek(pfile, 0x0012, SEEK_SET);fread(&imagewidth, sizeof(imagewidth), 1, pfile);fread(&imageheight, sizeof(imageheight), 1, pfile);pixellength = imagewidth * 3;while (pixellength % 4 != 0) pixellength++;pixellength *= imageheight;pixeldata = (GLubyte*)malloc(pixellength);if (pixeldata == 0) exit(0);fseek(pfile, 54, SEEK_SET);fread(pixeldata, pixellength, 1, pfile);p.push_back(pixeldata);fclose(pfile);
}//This function is used for some initializations. Such as enabling depth testing, texture and lighting.
//This function call "ReadImage" for the textures and set the texture objects.
//It also set the light.
void myinit(void) {glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LEQUAL);glShadeModel(GL_SMOOTH);glEnable(GL_TEXTURE_2D);ReadImage("kirbyFace.bmp", imageWidth[0], imageHeight[0], pixelLength[0]);ReadImage("kirbyFoot.bmp", imageWidth[1], imageHeight[1], pixelLength[1]);ReadImage("kirbyHand.bmp", imageWidth[2], imageHeight[2], pixelLength[2]);ReadImage("waddleFace.bmp", imageWidth[3], imageHeight[3], pixelLength[3]);ReadImage("waddleFoot.bmp", imageWidth[4], imageHeight[4], pixelLength[4]);ReadImage("waddleHand.bmp", imageWidth[5], imageHeight[5], pixelLength[5]);ReadImage("star.bmp", imageWidth[6], imageHeight[6], pixelLength[6]);ReadImage("water.bmp", imageWidth[7], imageHeight[7], pixelLength[7]);ReadImage("leftSky.bmp", imageWidth[8], imageHeight[8], pixelLength[8]);ReadImage("frontSky.bmp", imageWidth[9], imageHeight[9], pixelLength[9]);ReadImage("rightSky.bmp", imageWidth[10], imageHeight[10], pixelLength[10]);ReadImage("backSky.bmp", imageWidth[11], imageHeight[11], pixelLength[11]);ReadImage("topSky.bmp", imageWidth[12], imageHeight[12], pixelLength[12]);ReadImage("box.bmp", imageWidth[13], imageHeight[13], pixelLength[13]);ReadImage("specialBox.bmp", imageWidth[14], imageHeight[14], pixelLength[14]);glPixelStorei(GL_UNPACK_ALIGNMENT, 1);glGenTextures(textureNumber, &texture[0]);for (int i = 0; i < 15; i++){glBindTexture(GL_TEXTURE_2D, texture[i]);glTexImage2D(GL_TEXTURE_2D, 0, 3, imageWidth[i], imageHeight[i], 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, p[i]);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);}glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);GLfloat mat_shininess[] = { 50.0 };glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);GLfloat light_position[] = { light_x, light_y, light_z, 0.0 };glLightfv(GL_LIGHT0, GL_POSITION, light_position);glEnable(GL_LIGHTING);glEnable(GL_LIGHT0);
}//This function is used for making a skyBox for this game.
void skyBox()
{//WaterglBindTexture(GL_TEXTURE_2D, texture[7]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(-10.0, -0.9, 10.0);glTexCoord2f(0.0, 1.0); glVertex3f(-10.0, -0.9, -10.0);glTexCoord2f(1.0, 1.0); glVertex3f(10.0, -0.9, -10.0);glTexCoord2f(1.0, 0.0); glVertex3f(10.0, -0.9, 10.0);glEnd();//LeftSkyglBindTexture(GL_TEXTURE_2D, texture[8]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(-10.0, -2, 10.0);glTexCoord2f(0.0, 1.0); glVertex3f(-10.0, 18.0, 10.0);glTexCoord2f(1.0, 1.0); glVertex3f(-10.0, 18.0, -10.0);glTexCoord2f(1.0, 0.0); glVertex3f(-10.0, -2.0, -10.0);glEnd();//FrontSkyglBindTexture(GL_TEXTURE_2D, texture[9]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(-10.0, -2.0, -10.0);glTexCoord2f(0.0, 1.0); glVertex3f(-10.0, 18.0, -10.0);glTexCoord2f(1.0, 1.0); glVertex3f(10.0, 18.0, -10.0);glTexCoord2f(1.0, 0.0); glVertex3f(10.0, -2.0, -10.0);glEnd();//RightSkyglBindTexture(GL_TEXTURE_2D, texture[10]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(10.0, -2.0, -10.0);glTexCoord2f(0.0, 1.0); glVertex3f(10.0, 18.0, -10.0);glTexCoord2f(1.0, 1.0); glVertex3f(10.0, 18.0, 10.0);glTexCoord2f(1.0, 0.0); glVertex3f(10.0, -2.0, 10.0);glEnd();//BackSkyglBindTexture(GL_TEXTURE_2D, texture[11]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(10.0, -2.0, 10.0);glTexCoord2f(0.0, 1.0); glVertex3f(10.0, 18.0, 10.0);glTexCoord2f(1.0, 1.0); glVertex3f(-10.0, 18.0, 10.0);glTexCoord2f(1.0, 0.0); glVertex3f(-10.0, -2.0, 10.0);glEnd();//TopSkyglBindTexture(GL_TEXTURE_2D, texture[12]);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex3f(-10.0, 18.0, -10.0);glTexCoord2f(0.0, 1.0); glVertex3f(-10.0, 18.0, 10.0);glTexCoord2f(1.0, 1.0); glVertex3f(10.0, 18.0, 10.0);glTexCoord2f(1.0, 0.0); glVertex3f(10.0, 18.0, -10.0);glEnd();
}//This function is the animation for failure.
//When the game is over this function will be called and shows a star viewport for the failure reason
//It will be created outside of the viewport first and be shown when the game is over.
void scene()
{R = 10 - (10 * transitionTimer);glPushMatrix();glTranslatef(cameraPositionX + 0.0f, cameraPositionY - 0.4f, cameraPositionZ - 0.4f);glRotatef(-45.0f, 1.0f, 0.0f, 0.0f);for (int i = 0; i < 5; i++){glRotatef(72.0, 0.0, 0.0, 1.0);glBegin(GL_POLYGON);glVertex3f(0, R - sceneDisappear, 0);glVertex3f(0, 10 - sceneDisappear, 0);glVertex3f(-10, 10 - sceneDisappear, 0);glVertex3f(-10, R * cosf(72.0f * angleRadian) - sceneDisappear, 0);glVertex3f(-R * cosf(72.0f * angleRadian) / cosf(36.0f * angleRadian) * sinf(36.0f * angleRadian), R * cosf(72.0f * angleRadian) - sceneDisappear, 0);glEnd();}glPopMatrix();
}//The game will end in two ways: one is that Kirby has no haleth value, and the other is that Waddle has reached the center.
//This function is used for the first way.
//It will change the viewport to Kirby.
void kirbyDieEnd()
{cameraPositionX = kirbyPosition[0];cameraPositionY = kirbyPosition[1] + 1.0f;cameraPositionZ = kirbyPosition[2] + 1.0f;lookatPositionX = kirbyPosition[0];lookatPositionY = kirbyPosition[1];lookatPositionZ = kirbyPosition[2];
}//This function is used for the second way.
//It will change the viewport to the Waddle winner(similiar to Plants V.S. Zombies).
void waddleWinEnd()
{cameraPositionX = 0;cameraPositionY = 1.0f;cameraPositionZ = 1.0f;lookatPositionX = 0.0f;lookatPositionY = 0.0f;lookatPositionZ = 0.0f;
}//This function is used for game over.
//It will stop all the moving and focus on the the perspective of failure.
//It will call the corresponding ending method based on the reason for the end of the game.
void gameOver()
{gaming = false;sceneDisappear = 0.0f;waddleNumber += 4;for (int i = 0; i < 4; i++){waddleState[i] = 3;}if (kirbyLife <= 0){kirbyDieEnd();}else{waddleWinEnd();}
}//This function creates a star.
//It will be called for Kirby, Waddle and the star bullet.
//"R" is the radius of the star,"height: is the thickness of the star.
void star(float R, float height)
{glBindTexture(GL_TEXTURE_2D, texture[6]);float r = 1.8 * R / (sinf(36.0f * angleRadian) * cosf(18.0f * angleRadian) / sinf(18.0f * angleRadian) + cosf(36.0f * angleRadian));glPushMatrix();for (int i = 0; i < 5; i++){glRotatef(72.0, 0.0, 1.0, 0.0);glBegin(GL_TRIANGLES);glTexCoord2f(0.0f, 0.0f); glVertex3f(0.0f, height, 0.0f);glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 0.0f, R);glTexCoord2f(1.0f, 1.0f); glVertex3f(r * sin(36.0f * angleRadian), 0.0f, r * cos(36.0f * angleRadian));glTexCoord2f(0.0f, 0.0f); glVertex3f(0.0f, height, 0.0f);glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 0.0f, R);glTexCoord2f(1.0f, 1.0f); glVertex3f(-r * sin(36.0f * angleRadian), 0.0f, r * cos(36.0f * angleRadian));glTexCoord2f(0.0f, 0.0f); glVertex3f(0.0f, -height, 0.0f);glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 0.0f, R);glTexCoord2f(1.0f, 1.0f); glVertex3f(r * sin(36.0f * angleRadian), 0.0f, r * cos(36.0f * angleRadian));glTexCoord2f(0.0f, 0.0f); glVertex3f(0.0f, -height, 0.0f);glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 0.0f, R);glTexCoord2f(1.0f, 1.0f); glVertex3f(-r * sin(36.0f * angleRadian), 0.0f, r * cos(36.0f * angleRadian));glEnd();}glPopMatrix();
}//This function creates the body of Kirby.
//It will be called for creating Kirby.
void kirbyBody()
{glBindTexture(GL_TEXTURE_2D, texture[0]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);gluSphere(quadricObj, 0.16, 360, 360);gluDeleteQuadric(quadricObj);
}//This function creates the foot of Kirby.
//It will be called for creating Kirby.
void kirbyFoot()
{glBindTexture(GL_TEXTURE_2D, texture[1]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);glPushMatrix();glScalef(0.8f, 0.5f, 1.0f);gluSphere(quadricObj, 0.12, 360, 360);glPopMatrix();gluDeleteQuadric(quadricObj);
}//This function creates the hand of Kirby.
//It will be called for creating Kirby.
void kirbyHand()
{glBindTexture(GL_TEXTURE_2D, texture[2]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);glPushMatrix();glScalef(1.0, 0.6f, 0.6f);gluSphere(quadricObj, 0.08, 360, 360);glPopMatrix();gluDeleteQuadric(quadricObj);
}//This function uses hierarchical modeling to creat Kirby with the previous functions.
void kirby() {glPushMatrix();glTranslatef(kirbyPosition[0], kirbyPosition[1], kirbyPosition[2]);glRotatef(-kirbyAngle, 0.0, 1.0, 0.0);glRotatef(angleMoving, 1.0, 0.0, 0.0);glPushMatrix();glRotatef(-90.0, 1.0, 0.0, 0.0);kirbyBody();glPopMatrix();glPushMatrix();glTranslatef(0.16, 0.0, 0.0);kirbyHand();glTranslatef(-0.06, -0.16, 0.0);kirbyFoot();glPopMatrix();glPushMatrix();glTranslatef(-0.16, 0.0, 0.0);kirbyHand();glTranslatef(0.06, -0.16, 0.0);kirbyFoot();glPopMatrix();glPushMatrix();glTranslatef(0.0, -0.28, -0.06);star(0.36f, 0.1f);glPopMatrix();glPopMatrix();
}//This function is used for the animation of kirby hurt.
//When Kirby collides with Waddle, Kirby will bleed and be knocked unconscious for a while.
void kirbyHurt()
{kirbyAngle += 5.0f;kirbyAnimationTimer += 0.2f;
}//This function will check the state of Kirby.
//If Kirby is hurt, Kirby will be unconscious for a while.
//If Kirby is out of health value, it will call "gameOver" to end the game.
void kirbyStateCheck()
{if (kirbyAnimationTimer >= 10.0f){kirbyNormal = true;}if (kirbyLife <= 0){gameOver();}
}//This function creates the body of Waddle.
//It will be called for creating Waddle.
void waddleBody()
{glBindTexture(GL_TEXTURE_2D, texture[3]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);gluSphere(quadricObj, 0.16, 360, 360);gluDeleteQuadric(quadricObj);
}//This function creates the foot of Waddle.
//It will be called for creating Waddle.
void waddleFoot()
{glBindTexture(GL_TEXTURE_2D, texture[4]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);glPushMatrix();glScalef(0.8f, 0.5f, 1.0f);gluSphere(quadricObj, 0.12, 360, 360);glPopMatrix();gluDeleteQuadric(quadricObj);
}//This function creates the hand of Waddle.
//It will be called for creating Waddle.
void waddleHand()
{glBindTexture(GL_TEXTURE_2D, texture[5]);GLUquadric* quadricObj = gluNewQuadric();gluQuadricTexture(quadricObj, GL_TRUE);glPushMatrix();glScalef(1.0, 0.6f, 0.6f);gluSphere(quadricObj, 0.08, 360, 360);glPopMatrix();gluDeleteQuadric(quadricObj);
}//This function uses hierarchical modeling to creat Waddle with the previous functions.
//Unlike the previous method for creating Kirby, it rotates for showing Waddle is moving.
//"x","y","z" defines the position of Waddle.
//"angle" keeps Waddle can face to the center.
void waddle(float x, float y, float z, float angle) {glPushMatrix();glTranslated(x, y, z);glRotatef(-angle, 0, 1, 0);glRotatef(20.0, 1.0, 0.0, 0.0);glPushMatrix();glRotatef(-90.0, 1.0, 0.0, 0.0);waddleBody();glPopMatrix();glPushMatrix();glTranslatef(0.16, 0.0, 0.0);waddleHand();glTranslatef(-0.06, -0.16, 0.0);waddleFoot();glPopMatrix();glPushMatrix();glTranslatef(-0.16, 0.0, 0.0);waddleHand();glTranslatef(0.06, -0.16, 0.0);waddleFoot();glPopMatrix();glPushMatrix();glTranslatef(0.0, -0.28, -0.06);star(0.36f, 0.1f);glPopMatrix();glPopMatrix();
}//All waddles will be created in advance, but unseen because of the viewport.
//This function will find the first waiting Waddle which will be shown in the scene for its mission when waddleCreater is calling this function.
//The "waddleState" records the state of all the Waddles, 0 means it is dead, 1 means it is alive, 2 means it is dying.
//So if a new Waddle shoulbd be created a dead waddle index is needed.
int findAvaliableWaddle()
{for (int i = 0; i < 4; i++){if (waddleState[i] == 0){return i;}}return -1;
}//This function will find the alive waddle and used for kill it for cheating mode.
//It will be called in the cheating mode menu.
int findLiveWaddle() {for (int i = 0; i < 4; i++){if (waddleState[i] == 1){return i;}}return -1;
}//This function is used for creating a new Waddle or make a dead Waddle alive.
//It will call "findAvaliableWaddle" for creating an alive Waddle.
//"distance" defines the distance between Waddle and the center.
void waddleCreater(float distance)
{std::random_device rd;std::mt19937 gen(rd());std::uniform_int_distribution<> distr(-180, 180);int waddleIndex = findAvaliableWaddle();if (waddleIndex != -1){waddleAngle[waddleIndex] = distr(gen);waddlePosition[waddleIndex][1] = 0.0f;waddlePosition[waddleIndex][2] = -(distance * cos(-waddleAngle[waddleIndex] * angleRadian));waddlePosition[waddleIndex][0] = -(distance * sin(-waddleAngle[waddleIndex] * angleRadian));waddleState[waddleIndex] = 1;}
}//This function will make Waddles moving to the center.
void waddleMoving()
{for (int i = 0; i < 4; i++){if (waddleState[i] == 1){waddlePosition[i][2] += 0.005 * waddleMovingSpeed * cos(-waddleAngle[i] * angleRadian);waddlePosition[i][0] += 0.005 * waddleMovingSpeed * sin(-waddleAngle[i] * angleRadian);}}
}//This function will make Waddles moving faster.
//It will be called for the cheating mode menu.
void waddleMovingSpeedUp()
{if (waddleMovingSpeed < 20){waddleMovingSpeed += 1;}
}//This function will make Waddles moving slower.
//It will be called for the cheating mode menu.
void waddleMovingSpeedDown()
{if (waddleMovingSpeed > 1){waddleMovingSpeed -= 1;}
}//This function controls Waddle disappear animation
//It will be called when this Waddle is decided to be delete in "waddleDelete"
//"i" is the index of Waddle should be disappeared.
void waddleDisappear(int i)
{waddleAngle[i] += 5.0f;waddleAnimationTimer[i] += 0.2f;if (waddleAnimationTimer[i] >= 10.0f){waddleState[i] = 0;waddlePosition[i][0] = 0.0f;waddlePosition[i][1] = -10.0f;waddlePosition[i][2] = 0.0f;waddleAngle[i] = 0.0f;waddleNumber -= 1;}
}//This function will decide which waddle will be deleted and the reason for deleting it.
//If it is attacked by the bullet star it will disappear normally.
//If it is collision with Kirby, it will make kirby hurt with losing one health value.
//If it is arriving to the center, it will call "gameOver" to end the game.
void waddleDelete()
{for (int i = 0; i < 4; i++){float starDistanceSquare = (waddlePosition[i][0] - starBulletPosition[0]) * (waddlePosition[i][0] - starBulletPosition[0]) + (waddlePosition[i][2] - starBulletPosition[2]) * (waddlePosition[i][2] - starBulletPosition[2]);float kirbyDistanceSquare = (waddlePosition[i][0] - kirbyPosition[0]) * (waddlePosition[i][0] - kirbyPosition[0]) + (waddlePosition[i][2] - kirbyPosition[2]) * (waddlePosition[i][2] - kirbyPosition[2]);float winDistanceSquare = waddlePosition[i][0] * waddlePosition[i][0] + waddlePosition[i][2] * waddlePosition[i][2];if (winDistanceSquare <= 0.01 && waddleState[i] == 1){gameOver();}if (starDistanceSquare <= 0.08 && waddleState[i] == 1){waddleAnimationTimer[i] = 0.0f;waddleState[i] = 2;}if (kirbyDistanceSquare <= 0.08 && waddleState[i] == 1){waddleAnimationTimer[i] = 0.0f;waddleState[i] = 2;kirbyLife -= 1;kirbyAnimationTimer = 0.0f;kirbyNormal = false;}if (waddleState[i] == 2){waddleDisappear(i);}}
}//This function creates a star bullet.
void starBullet()
{glPushMatrix();glTranslatef(starBulletPosition[0], starBulletPosition[1], starBulletPosition[2]);glRotatef(-starBulletAngle, 0.0, 1.0, 0.0);glRotatef(-90.0, 1.0, 0, 0);star(0.18f, 0.05f);glPopMatrix();
}//This function will make star bullet start moving from the position of K0irby.
//It will be called with the user press "E" or "Space".
void starAttack()
{starBulletMoving = true;for (int i = 0; i < 3; i++){starBulletPosition[i] = kirbyPosition[i];}starBulletAngle = kirbyAngle;
}//This function is used for making the bullet dissapear and avoid the error of distance between the star bullet with Waddle.
void starDisappear()
{starBulletMoving = false;starBulletPosition[1] = -10.0f;
}//This function controls the moving of star bullet.
//It will call "starDisappear" for avoiding the bullet out of range.
void starMoving()
{if (gaming){starBulletPosition[2] += 0.1 * cos(-starBulletAngle * angleRadian);starBulletPosition[0] += 0.1 * sin(-starBulletAngle * angleRadian);if (std::abs(starBulletPosition[2]) > 8.5f){starDisappear();}if (std::abs(starBulletPosition[0]) > 8.5f){starDisappear();}}
}//It creats the box for decorating the game scene.
// "x","y","z" defines the postion of the box.
// "size" difines the half length of the box.
//It will be called in "boxScene".
void box(float x, float y, float z, float size)
{glBindTexture(GL_TEXTURE_2D, texture[13]);glBegin(GL_QUADS);//frontglTexCoord2f(0.0, 0.0); glVertex3f(x - size, y - size, z + size);glTexCoord2f(0.0, 1.0); glVertex3f(x - size, y + size, z + size);glTexCoord2f(1.0, 1.0); glVertex3f(x + size, y + size, z + size);glTexCoord2f(1.0, 0.0); glVertex3f(x + size, y - size, z + size);//backglTexCoord2f(0.0, 0.0); glVertex3f(x + size, y - size, z - size);glTexCoord2f(0.0, 1.0); glVertex3f(x + size, y + size, z - size);glTexCoord2f(1.0, 1.0); glVertex3f(x - size, y + size, z - size);glTexCoord2f(1.0, 0.0); glVertex3f(x - size, y - size, z - size);//leftglTexCoord2f(0.0, 0.0); glVertex3f(x + size, y - size, z + size);glTexCoord2f(0.0, 1.0); glVertex3f(x + size, y + size, z + size);glTexCoord2f(1.0, 1.0); glVertex3f(x + size, y + size, z - size);glTexCoord2f(1.0, 0.0); glVertex3f(x + size, y - size, z - size);//rightglTexCoord2f(0.0, 0.0); glVertex3f(x - size, y - size, z - size);glTexCoord2f(0.0, 1.0); glVertex3f(x - size, y + size, z - size);glTexCoord2f(1.0, 1.0); glVertex3f(x - size, y + size, z + size);glTexCoord2f(1.0, 0.0); glVertex3f(x - size, y - size, z + size);//topglTexCoord2f(0.0, 0.0); glVertex3f(x - size, y + size, z + size);glTexCoord2f(0.0, 1.0); glVertex3f(x - size, y + size, z - size);glTexCoord2f(1.0, 1.0); glVertex3f(x + size, y + size, z - size);glTexCoord2f(1.0, 0.0); glVertex3f(x + size, y + size, z + size);//bottomglTexCoord2f(0.0, 0.0); glVertex3f(x - size, y - size, z - size);glTexCoord2f(0.0, 1.0); glVertex3f(x - size, y - size, z + size);glTexCoord2f(1.0, 1.0); glVertex3f(x + size, y - size, z + size);glTexCoord2f(1.0, 0.0); glVertex3f(x + size, y - size, z - size);glEnd();
}//It creates the defence target, which is the gift for friend's birthday.
//"y" controls its position.
//"size" difines the half length of the box.
void specialBox(float y, float size)
{glBindTexture(GL_TEXTURE_2D, texture[14]);glBegin(GL_QUADS);//frontglTexCoord2f(0.0, 0.0); glVertex3f(0 - size, y - size, 0 + size);glTexCoord2f(0.0, 1.0); glVertex3f(0 - size, y + size, 0 + size);glTexCoord2f(1.0, 1.0); glVertex3f(0 + size, y + size, 0 + size);glTexCoord2f(1.0, 0.0); glVertex3f(0 + size, y - size, 0 + size);//backglTexCoord2f(0.0, 0.0); glVertex3f(0 + size, y - size, 0 - size);glTexCoord2f(0.0, 1.0); glVertex3f(0 + size, y + size, 0 - size);glTexCoord2f(1.0, 1.0); glVertex3f(0 - size, y + size, 0 - size);glTexCoord2f(1.0, 0.0); glVertex3f(0 - size, y - size, 0 - size);//leftglTexCoord2f(0.0, 0.0); glVertex3f(0 + size, y - size, 0 + size);glTexCoord2f(0.0, 1.0); glVertex3f(0 + size, y + size, 0 + size);glTexCoord2f(1.0, 1.0); glVertex3f(0 + size, y + size, 0 - size);glTexCoord2f(1.0, 0.0); glVertex3f(0 + size, y - size, 0 - size);//rightglTexCoord2f(0.0, 0.0); glVertex3f(0 - size, y - size, 0 - size);glTexCoord2f(0.0, 1.0); glVertex3f(0 - size, y + size, 0 - size);glTexCoord2f(1.0, 1.0); glVertex3f(0 - size, y + size, 0 + size);glTexCoord2f(1.0, 0.0); glVertex3f(0 - size, y - size, 0 + size);//topglTexCoord2f(0.0, 0.0); glVertex3f(0 - size, y + size, 0 + size);glTexCoord2f(0.0, 1.0); glVertex3f(0 - size, y + size, 0 - size);glTexCoord2f(1.0, 1.0); glVertex3f(0 + size, y + size, 0 - size);glTexCoord2f(1.0, 0.0); glVertex3f(0 + size, y + size, 0 + size);//bottomglTexCoord2f(0.0, 0.0); glVertex3f(0 - size, y - size, 0 - size);glTexCoord2f(0.0, 1.0); glVertex3f(0 - size, y - size, 0 + size);glTexCoord2f(1.0, 1.0); glVertex3f(0 + size, y - size, 0 + size);glTexCoord2f(1.0, 0.0); glVertex3f(0 + size, y - size, 0 - size);glEnd();
}//It creates some box with "box" functions to decorate the game scene.
//"y" will combines with the global variable "angleWave" to control the y value for the box to make the box up and down like function of the waves.
void boxScene(float y)
{box(3.0, 0.1 * cos(y) - 1.0, 7.0, 0.15);box(2.0, 0.2 * cos(y) - 1.0, 3.0, 0.15);box(-5.0, 0.2 * cos(y) - 1.0, -6.5, 0.15);box(-8.0, 0.1 * cos(y) - 1.0, 1.0, 0.15);glRotatef(30.0, 0.0, 1.0, 0.0);box(-1.0, 0.1 * cos(y) - 1.0, 7.0, 0.15);box(7.0, 0.2 * cos(y) - 1.0, -1.0, 0.15);box(4.0, 0.1 * cos(y) - 1.0, 6.0, 0.15);box(6.0, 0.2 * cos(y) - 1.0, -3.2, 0.15);glRotatef(15.0, 0.0, 1.0, 0.0);box(-1.4, 0.15 * cos(y) - 1.0, 2.0, 0.15);box(8.8, 0.25 * cos(y) - 1.0, -0.7, 0.15);box(0.9, 0.1 * cos(y) - 1.0, -6.0, 0.15);box(1.8, 0.2 * cos(y) - 1.0, -3.2, 0.15);box(-3.0, 0.15 * cos(y) - 1.0, 5.0, 0.15);box(-4.0, 0.25 * cos(y) - 1.0, 3.0, 0.15);box(-5.0, 0.1 * cos(y) - 1.0, 4.1, 0.15);box(5.5, 0.2 * cos(y) - 1.0, 2.9, 0.15);box(-2.7, 0.1 * cos(y) - 1.0, 1.1, 0.15);box(-1.8, 0.2 * cos(y) - 1.0, -3.4, 0.15);glRotatef(-45.0, 0.0, 1.0, 0.0);
}//This function will add "times" health value for Kirby.
//It will be called in the cheating mode menu.
void lifeCheat(int times)
{for (int i = 0; i < times; i++){kirbyLife += 1;}
}//This function will kill one alive Waddle.
//It calls "findLiveWaddle" for finding an alive one.
//It will be called in the cheating mode menu.
void killCheat()
{int killIndex = findLiveWaddle();if (killIndex != -1){waddleAnimationTimer[killIndex] = 0.0f;waddleState[killIndex] = 2;waddleDisappear(killIndex);}
}//This function will kill all alive Waddle.
//It calls "killCheat" four times to kill all alive Waddle.
//It will be called in the cheating mode menu.
void killAllCheat()
{for (int i = 0; i < 4; i++){killCheat();}
}//Update some value for the whole game.
//It also constantly checks some values that control the game's operation, in order to control the game to develop in the prescribed logical direction.
void update(int value)
{angleWave += 0.01 * angleRadian;kirbyStateCheck();waddleMoving();waddleDelete();if (waddleNumber < 5 && timer >= 20.0){waddleNumber += 1;waddleCreater(8.0f);timer = 0;}timer += 0.2;if (kirbyNormal != true){kirbyHurt();}if (starBulletMoving == true){starMoving();}glutPostRedisplay();glutTimerFunc(1000 / 60, update, 0);if (gaming != true) {transitionTimer += 0.01f;}if (gaming){cameraPositionX = lookatPositionX + cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = lookatPositionY + cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = lookatPositionZ + cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);lookatPositionX = kirbyPosition[0];lookatPositionY = kirbyPosition[1];lookatPositionZ = kirbyPosition[2];}
}//This function defines the content that needs to be drawn for each frame.
void display()
{glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);glLoadIdentity();gluLookAt(cameraPositionX, cameraPositionY, cameraPositionZ, lookatPositionX, lookatPositionY, lookatPositionZ, 0.0, 1.0, 0.0);kirby();skyBox();starBullet();waddle(waddlePosition[0][0], waddlePosition[0][1], waddlePosition[0][2], waddleAngle[0]);waddle(waddlePosition[1][0], waddlePosition[1][1], waddlePosition[1][2], waddleAngle[1]);waddle(waddlePosition[2][0], waddlePosition[2][1], waddlePosition[2][2], waddleAngle[2]);waddle(waddlePosition[3][0], waddlePosition[3][1], waddlePosition[3][2], waddleAngle[3]);scene();specialBox(-0.7, 0.15);boxScene(angleWave * angleRadian);glutSwapBuffers();
}//This function is used to update the viewport and projection.
void reshape(GLsizei w, GLsizei h)
{glViewport(0, 0, w, h);glMatrixMode(GL_PROJECTION);glLoadIdentity();gluLookAt(cameraPositionX, cameraPositionY, cameraPositionZ, lookatPositionX, lookatPositionY, lookatPositionZ, 0.0, 1.0, 0.0);gluPerspective(90.0, 4.0f / 3.0f, 0.01f, 1000.0f);glMatrixMode(GL_MODELVIEW);glLoadIdentity();}//Following two functions provides the menu for the user.
//In the menu the user can get the help for how to play the game.
//In the menu the user can cheat with the cheat operations submenu for the cheating modes.
//In the menu the user can quit the game directly.
//This function will react the operation with the corresponding "num" value.
void menu(int num) {switch (num) {case 1:exit(0);break;case 2:killCheat();break;case 3:killAllCheat();break;case 4:if (kirbyLife < 3){lifeCheat(3 - kirbyLife);}break;case 5:lifeCheat(10);break;case 6:lifeCheat(100);break;case 7:lifeCheat(999);break;case 8:break;case 9:waddleMovingSpeedUp();break;case 10:waddleMovingSpeedDown();break;}
}//This function initials and creates the menus.
void initMenu() {int help = glutCreateMenu(menu);glutAddMenuEntry("w: Kirby go staight", 8);glutAddMenuEntry("s: Kirby go back", 8);glutAddMenuEntry("a: Kirby turn left", 8);glutAddMenuEntry("d: kirby turn right", 8);glutAddMenuEntry("Using direction keys controls the camera", 8);glutAddMenuEntry("e / space: Launch stars", 8);glutAddMenuEntry("esc: Quit the game", 8);int difficultyCheatOptions = glutCreateMenu(menu);glutAddMenuEntry("Difficulty up", 9);glutAddMenuEntry("Difficulty down", 10);int killCheatOptions = glutCreateMenu(menu);glutAddMenuEntry("Kill one", 2);glutAddMenuEntry("Kill all", 3);int lifeCheatOptions = glutCreateMenu(menu);glutAddMenuEntry("Life max", 4);glutAddMenuEntry("Life +10", 5);glutAddMenuEntry("Life +100", 6);glutAddMenuEntry("Life +999", 7);int cheatOptions = glutCreateMenu(menu);glutAddSubMenu("Kill cheat options:", killCheatOptions);glutAddSubMenu("Life cheat options:", lifeCheatOptions);glutAddSubMenu("Difficulty cheat options:", difficultyCheatOptions);int menuID = glutCreateMenu(menu);glutAddSubMenu("Operation instructions:", help);glutAddSubMenu("Cheat options:", cheatOptions);glutAddMenuEntry("Quit the game(Esc)", 1);glutAttachMenu(GLUT_RIGHT_BUTTON);
}//This function controls the interaction with special keys in the keyboard.
void specialKeys(int key, int x, int y)
{//The up arrow key can move the camera upwards.if (key == GLUT_KEY_UP && gaming == true && cameraVerticalAngle <= 89.0f * angleRadian){cameraVerticalAngle += 1.0f * angleRadian;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}//The down arrow key can move the camera downwardsif (key == GLUT_KEY_DOWN && gaming == true && cameraVerticalAngle >= -20.0f * angleRadian){cameraVerticalAngle -= 1.0f * angleRadian;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}//The left arrow key can make the camera turn leftif (key == GLUT_KEY_LEFT && gaming == true){cameraHorizontalAngle -= 1.0f * angleRadian;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}//The right arrow key can make the camera turn rightif (key == GLUT_KEY_RIGHT && gaming == true){cameraHorizontalAngle += 1.0f * angleRadian;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}glutPostRedisplay();
}//This function controls the interaction with normal keys in the keyboard.
void keyboardInput(unsigned char key, int x, int y)
{//Press 'W' or 'S' will make Kirby forward or backward.//Kirby will tilt towards the direction of motion to make it more like it is in motion.//Press 'A' or 'D' will make Kirby turn left or right.//Press 'E' or 'Space' to shoot the star bullet.//Press 'Esc' to quit the game.angleMoving = 0.0f;if (((key == 'w' || key == 'W') && kirbyNormal == true) && gaming == true){angleMoving = 20.0f;if ((std::abs(kirbyPosition[2] + 0.05 * cos(-kirbyAngle * angleRadian)) < 8.5) && (std::abs(kirbyPosition[0] + 0.05 * cos(-kirbyAngle * angleRadian)) < 8.5)){kirbyPosition[2] += 0.05 * cos(-kirbyAngle * angleRadian);kirbyPosition[0] += 0.05 * sin(-kirbyAngle * angleRadian);}}if (((key == 's' || key == 'S') && kirbyNormal == true) && gaming == true){angleMoving = -20.0f;if ((std::abs(kirbyPosition[2] - 0.05 * cos(-kirbyAngle * angleRadian)) < 8.5) && (std::abs(kirbyPosition[0] - 0.05 * cos(-kirbyAngle * angleRadian)) < 8.5)){kirbyPosition[2] -= 0.05 * cos(-kirbyAngle * angleRadian);kirbyPosition[0] -= 0.05 * sin(-kirbyAngle * angleRadian);}}if (((key == 'a' || key == 'A') && kirbyNormal == true) && gaming == true){kirbyAngle -= 1;}if (((key == 'd' || key == 'D') && kirbyNormal == true) && gaming == true){kirbyAngle += 1;}if ((((key == 'e' || key == 'E' || key == ' ') && starBulletMoving != true) && kirbyNormal == true) && gaming == true){starAttack();}if (key == 27){exit(0);}glutPostRedisplay();}//This function controls the interation with the mouse.
void mouse(int button, int state, int x, int y)
{//Sliding the roller upwards will enlarge.if (button == 3 && gaming == true && cameraDistance > 0.4f){cameraDistance -= 0.1f;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}//Sliding the roller downwards will shrink.else if (button == 4 && gaming == true && cameraDistance < 2.0f){cameraDistance += 0.1f;cameraPositionX = cameraDistance * cos(cameraVerticalAngle) * sin(cameraHorizontalAngle), cameraPositionY = cameraDistance * sin(cameraVerticalAngle), cameraPositionZ = cameraDistance * cos(cameraVerticalAngle) * cos(cameraHorizontalAngle);}glutPostRedisplay();
}//Set the windown size and some callback function
int main(int argc, char** argv)
{glutInit(&argc, argv);glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);glutInitWindowPosition(100, 100);glutInitWindowSize(800, 600);glutCreateWindow("Kirby Defends Gift");glutDisplayFunc(display);glutReshapeFunc(reshape);glutSpecialFunc(specialKeys);glutMouseFunc(mouse);glutKeyboardFunc(keyboardInput);glutTimerFunc(0, update, 0);initMenu();myinit();glutMainLoop();return 0;
}

相关文章:

CPT205 计算机图形学 OpenGL 3D实践(CW2)

文章目录 1. 介绍2. 设计3. 准备阶段4. 角色构建5. 场景构建6. 交互部分6.1 键盘交互6.2 鼠标交互6.3 鼠标点击出多级菜单进行交互 7. 缺点与问题7.1 程序bug7.2 游戏乐趣不足7.3 画面不够好看 8. 完整代码 1. 介绍 前面已经分享过了关于CPT205的CW1的2D作业&#xff0c;这次C…...

Netty的基本架构详解

EventLoopGroup基本认识 我们需要了解的 EventLoopGroup, Netty对EventLoopGroup做了很多的扩展实现&#xff0c;下图是他的家族图谱&#xff1a; 我们上一节课使用的案例&#xff0c;使用的是NioEventLoopGroup&#xff0c;他是NIO的实现&#xff0c;可以看出来他是Multithre…...

2025前端面试题超全面解析(附答案与深度扩展)

文章目录 一、HTML篇&#xff08;扩展版&#xff09;1. **HTML5语义化标签的实际应用场景**2. **Web Components实战&#xff1a;如何封装一个自定义按钮组件&#xff1f;**3. **Web Worker的用途与限制** 二、CSS篇&#xff08;扩展版&#xff09;1. **CSS盒模型详解&#xff…...

自己搭建可以和deepseek对话的WEB应用

第一步 下载安装anaconda&#xff0c;地址&#xff1a;https://repo.anaconda.com/ 第二步 打开anaconda客户端&#xff0c;打开conda命令行窗口 第三步 创建一个open-webui专属的python专属的虚拟环境&#xff0c;并且指定python具体的版本 conda create --name open…...

Linux系统运行模式和链接

一、系统运行模式 centos6 0 关机模式 1 单用户模式 2 字符模式&#xff0c;无网络连接 3 字符模式 4 预留 5 图形模式 6 重启模式 查看系统当前处于的运行模式 切换为图形模式 init 5 centos7 字符模式 multi-user…...

.NET 9.0 的 Blazor Web App 项目,进度条 <progress> 组件使用注意事项

一、执行过程中&#xff0c;要刷新 进度条 的显示&#xff0c;需要 延时、释放&#xff0c;否则进度条不 实时 更新&#xff0c;最后一下到 100% // 延时&#xff0c;释放给前端&#xff1a;【必须】&#xff0c;否则进度条不 实时 更新&#xff0c;最后一下到 100await Task.D…...

头歌实验--面向对象程序设计

目录 实验五 类的继承与派生 第1关&#xff1a;简易商品系统 任务描述 答案代码 第2关&#xff1a;公司支出计算 任务描述 答案代码 第3关&#xff1a;棱柱体问题 任务描述 答案代码 实验五 类的继承与派生 第1关&#xff1a;简易商品系统 任务描述 答案代码 #incl…...

IoTDB 断电后无法启动 DataNode,日志提示 Meet error while starting up

问题 IoTDB 1.3.2 版本&#xff0c;断电后 IoTDB 的 DataNode 无法启动&#xff0c;日志如下&#xff1a; 2024-12-16 14:45:41,350 [main] ERROR o.a.i.db.service.DataNode:562 - Meet error while starting up. org.apache.iotdb.commons.exception.StartupException: Fo…...

2024华为OD机试真题-最大报酬(C++)-E卷B卷-100分

2024华为OD机试最新题库-(C卷+D卷+E卷)-(JAVA、Python、C++) 目录 题目描述 输入描述 输出描述 示例一 解题思路 考点 代码 c++ 题目描述 小明每周上班都会拿到自己的工作清单,工作清单内包含n项工作, 每项工作都有对应的耗时时间(单位h)和报酬, 工作的总报酬为…...

jenkins war Windows安装

Windows安装Jenkins 需求1.下载jenkins.war2.编写快速运行脚本3.启动Jenkins4.Jenkins使用 需求 1.支持在Windows下便捷运行Jenkins&#xff1b; 2.支持自定义启动参数&#xff1b; 3.有快速运行的脚步样板。 1.下载jenkins.war Jenkins下载地址&#xff1a;https://get.j…...

HCIA项目实践--RIP相关原理知识面试问题总结回答

9.4 RIP 9.4.1 补充概念 什么是邻居&#xff1f; 邻居指的是在网络拓扑结构中与某一节点&#xff08;如路由器&#xff09;直接相连的其他节点。它们之间可以直接进行通信和数据交互&#xff0c;能互相交换路由信息等&#xff0c;以实现网络中的数据转发和路径选择等功能。&am…...

用大模型学大模型04-模型与网络

目前已经学完深度学习的数学基础&#xff0c;开始学习各种 模型和网络阶段&#xff0c;给出一个从简单到入门的&#xff0c;层层递进的学习路线。并给出学习每种模型需要的前置知识。增加注意力机制&#xff0c;bert, 大模型&#xff0c;gpt, transformer&#xff0c; MOE等流行…...

浏览器扩展实现网址自动替换

作为一个开发爱好者&#xff0c;不能顺畅访问github是很痛苦的&#xff0c;这种状况不知道何时能彻底解决。 目前也有很多方案可以对应这种囧况&#xff0c;我此前知道有一个网站kkgithub&#xff0c;基本上把github的静态内容都搬了过来&#xff0c;我们如果需要访问某个githu…...

适配器模式详解(Java)

一、引言 1.1 定义与类型 适配器模式是一种结构型设计模式,主要目的是将一个类的接口转换为客户期望的另一个接口。这种模式使得原本因为接口不匹配而不能一起工作的类可以一起工作,从而提高了类的复用性。适配器模式分为类适配器和对象适配器两种类型。类适配器使用继承关…...

C语言表驱动法

最近了解到一种C语言的写法&#xff0c;故记录下来&#xff0c;内容来自deepseek。 表驱动法 表驱动法&#xff08;Table-Driven Approach&#xff09;是一种编程技术&#xff0c;通过使用表格&#xff08;数组、结构体数组、哈希表等&#xff09;来存储数据或逻辑&#xff0…...

【鸿蒙Next】优秀鸿蒙博客集锦

鸿蒙基础开发&#xff1a;多文件压缩上传及断点续传_鸿蒙 断点续传-CSDN博客...

Django REST Framework:如何获取序列化后的ID

Django REST Framework&#xff1a;如何获取序列化后的ID &#x1f604; 嗨&#xff0c;小伙伴们&#xff01;今天我们来聊一聊Django REST Framework&#xff08;简称DRF&#xff09;中一个非常常见的操作&#xff1a;如何获取序列化后的ID。对于那些刚入门的朋友们&#xff…...

deep seek

1.介绍:DeepSeek是一款由国内人工智能公司研发的大型语言模型&#xff0c;拥有强大的自然语言处理能力&#xff0c;能够理解并回答问题&#xff0c;还能辅助写代码、整理资料和解决复杂的数学问题。免费开源&#xff0c;媲美ChatGPT 最近最火爆的AI对话程序。 www.deepseek.com…...

前端设计模式介绍及案例(单例模式、代理模式、工厂模式、装饰者模式、观察者模式)

概要 本文主要介绍了前端设计模式的定义、分类以及常用设计模式的具体案例。 前言 使用设计模式的目的&#xff1a;为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化&#xff1b;设计模式是软件工程的基石脉络&#xff0c;如同大厦…...

开源堡垒机 JumpServer 社区版实战教程:基于 Ubuntu 22.04 离线安装 JumpServer 社区版 v4.4.1

文章目录 开源堡垒机 JumpServer 社区版实战教程&#xff1a;基于 Ubuntu 22.04 离线安装 JumpServer 社区版 v4.4.1一、环境要求1.1 操作系统1.1.1 Ubuntu1.1.2 CentOS 1.2 数据库1.2.1 JumpServer 需要使用的数据库1.2.2 创建数据库 SQL 参考1.2.2.1 PostgreSQL1.2.2.2 MySQL…...

电源测试和测量系统的创新遥感方法可以消除哪些潜在问题

传统的遥感方法 远程感测是一种行之有效的方法&#xff0c;通过消除连接电缆中压降的影响来调节负载点的直流功率。这在测试和测量应用中尤其重要&#xff0c;在这些应用中&#xff0c;电源电压在一系列负载条件下的准确性和一致性通常对于获得准确且可重复的测试结果至关重要…...

10、《Thymeleaf模板引擎:动态页面开发全攻略》

Thymeleaf模板引擎&#xff1a;动态页面开发全攻略 一、Thymeleaf核心价值解析 天然HTML亲和力&#xff1a;Thymeleaf允许直接使用.html文件作为模板&#xff0c;支持浏览器直接预览静态原型&#xff0c;同时通过属性标签&#xff08;如th:text&#xff09;实现动态渲染&…...

Day1 25/2/14 FRI

【一周刷爆LeetCode&#xff0c;算法大神左神&#xff08;左程云&#xff09;耗时100天打造算法与数据结构基础到高级全家桶教程&#xff0c;直击BTAJ等一线大厂必问算法面试题真题详解&#xff08;马士兵&#xff09;】https://www.bilibili.com/video/BV13g41157hK?p3&v…...

untiy3D 让角色动起来,角色动画的使用

1.untiy 商店下载动画模型 2.导入项目 模型拖入到场景中 3.创建动画器控制器 4.动画控制器挂载到plarer上 5.把动画idle和pickup拖入到动画器 6.右键动画创建过渡效果(Make Transition) 6.设置参数用条件控制 7.当选中参数时启动过渡 运行效果 119 (二)用脚本控制动画…...

Word 里面嵌入DeepSeek

目录 一、问题描述 二、解决方法 三、代码 四、注意事项 五、总结 一、问题描述 如何在Word里面嵌入DeepSeek? 二、解决方法 1、新建文档&#xff0c;按 AltF11&#xff0c;进入VB界面。 2、选中文档&#xff0c;右键->插入->模块。 3、进入模块&#xff0c;粘入…...

深入浅出Java反射:掌握动态编程的艺术

小程一言反射何为反射反射核心类反射的基本使用获取Class对象创建对象调用方法访问字段 示例程序应用场景优缺点分析优点缺点 注意 再深入一些反射与泛型反射与注解反射与动态代理反射与类加载器 结语 小程一言 本专栏是对Java知识点的总结。在学习Java的过程中&#xff0c;学习…...

exr 格式下 全景图(经纬图、panorama)转 cubemap

先上效果 &#xff08;X, -X, Y, -Y, Z, -Z&#xff09; 下载 exr 经纬图 笔者用的这张&#xff1a;https://polyhaven.com/a/kloofendal_48d_partly_cloudy_puresky 使用 Openexr 的 exrenvmap 工具 下载 我 build 了一份 3.3.2 版本的&#xff0c;免积分下载。 https:/…...

解锁建造者模式:Java 编程中的对象构建秘籍

系列文章目录 后续补充~~~~ 文章目录 一、引言二、建造者模式原理剖析2.1 定义与概念2.2 模式结构与角色2.2.1 产品(Product)2.2.2 建造者(Builder)2.2.3 具体建造者(ConcreteBuilder)2.2.4 指挥者(Director)2.3 工作流程与交互机制三、建造者模式在 Java 中的优势3.1 …...

ArcGIS Pro显示缓存空间不足导致编辑或加载数据显示不完全

ArcGIS Pro对于显示缓存有32GB的限制&#xff0c;所以当缓存设置中&#xff0c;缓存将达到32GB时&#xff0c;会出现编辑、加载slpk显示不全的情况。 清除计算机上的显示缓存方法 1.启动 ArcGlS Pro。单击左下角的设置&#xff0c;然后单击选项&#xff1b; 2.在选项窗口中&…...

大数据、云计算、人工智能等技术深度融合的智慧快消开源了。

智慧快消视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。 基于多年的深度…...

C++17 中的 std::reduce:详细教程

文章目录 1. 简介2. 函数签名3. 使用场景3.1 简单的累加操作3.2 自定义归并操作3.3 并行计算的性能优势 4. 注意事项4.1 归并操作的结合律和交换律4.2 默认值的使用 5. 总结 1. 简介 std::reduce 是 C17 标准库中引入的一个算法&#xff0c;用于对范围内的元素进行归并操作。它…...

Python爬虫实战:获取笔趣阁图书信息,并做数据分析

注意:以下内容仅供技术研究,请遵守目标网站的robots.txt规定,控制请求频率避免对目标服务器造成过大压力! 1. 环境准备与反爬策略 python import requests from bs4 import BeautifulSoup import pandas as pd import re import time import random from fake_useragent …...

win11系统 Docker Desktop提示Docker Engine stopped解决全过程记录

DockerDesktop安装指南以及Windows下WSL2和 Hyper-V相关问题追查 【已解决】win10系统 Docker 提示Docker Engine stopped解决全过程记录 本篇文章主要记录Docker Desktop安装和使用时出现的问题及解决方法&#xff0c;以及后续使用夜神模拟器&#xff0c;关闭了Hyper-V时&am…...

c# sqlite 批量生成insert语句的函数

函数开始 using System; using System.Collections.Generic; using System.Text;public class SqliteHelper {public static List<string> GenerateInsertStatements(string tableName, List<string> columns, List<List<object>> data){List<stri…...

ASP.NET Core SixLabors.ImageSharp v3.x 的图像实用程序类

使用用 C# 编写的 asp.net core web 应用程序示例在 Windows 和 Linux web 服务器上处理图像&#xff0c;包括创建散点图和直方图&#xff0c;以及根据需要旋转图像以便正确显示。 这个小型实用程序库需要将 NuGet SixLabors.ImageSharp包&#xff08;版本 3.1.x&#xff09;添…...

湖仓分析|浙江霖梓基于 Doris + Paimon 打造实时/离线一体化湖仓架构

导读&#xff1a;浙江霖梓早期使用 CDH 产品套件搭建了大数据系统&#xff0c;面临业务逻辑冗余、查询效率低下等问题&#xff0c;基于 Apache Doris 进行整体架构与表结构的重构&#xff0c;并基于湖仓一体和查询加速展开深度探索与实践&#xff0c;打造了 Doris Paimon 的实…...

【AI-34】机器学习常用七大算法

以下是对这七大常用算法的浅显易懂解释&#xff1a; 1. k 邻近算法&#xff08;k - Nearest Neighbors&#xff0c;KNN&#xff09; 想象你在一个满是水果的大广场上&#xff0c;现在有个不认识的水果&#xff0c;想知道它是什么。k 邻近算法就是去看离这个水果最近的 k 个已…...

2025年金三银四经典自动化测试面试题

概述 觉得自动化测试很难&#xff1f; 是的&#xff0c;它确实不简单。但是学会它&#xff0c;工资高啊&#xff01; 担心面试的时候被问到自动化测试&#xff1f; 嗯&#xff0c;你担心的没错&#xff01;确实会被经常问到&#xff01; 现在应聘软件测试工程师的岗位&…...

遵循规则:利用大语言模型进行视频异常检测的推理

文章目录 速览摘要01 引言02 相关工作视频异常检测大语言模型 03 归纳3.1 视觉感知3.2 规则生成Normal and Anomaly &#xff08;正常与异常&#xff09;Abstract and Concrete &#xff08;抽象与具体&#xff09;Human and Environment &#xff08;人类与环境&#xff09; 3…...

RFM模型-数据清洗

在进行数据清洗时&#xff0c;主要的目标是确保数据质量良好&#xff0c;以便后续的分析和建模工作能够顺利进行。针对你使用粒子群优化算法改进RFM模型来对电商数据进行用户群像划分的实验&#xff0c;数据清洗环节尤其重要&#xff0c;因为不干净的数据会影响模型的精度和效果…...

文件系统惹(细)

目录 块概念 分区 inode ext2文件系统 Boot Sector Super Block GDP&#xff08;group descriptor table&#xff09; Block Bitmap&#xff08;块位图&#xff09; Inode Bitmap &#xff08;inode位图&#xff09; Data Block inode和Datablock映射 目录和文件名 …...

中望CAD c#二次开发 ——VS环境配置

新建类库项目&#xff1a;下一步 下一步 下一步&#xff1a; 或直接&#xff1a; 改为&#xff1a; <Project Sdk"Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>NET48</TargetFramework> <LangVersion>pr…...

Centos7安装Clickhouse单节点部署

​ 部署流程 1、关闭防火墙&沙盒 关闭防火墙并关闭开机自启动 systemctl stop firewalld && systemctl disable firewalld查看selinux状态是否为disabled&#xff0c;否则修改 [rootlocalhost ~]# getenforce Enforcing修改为disabled vim /etc/selinux/config…...

4.SpringSecurity在分布式环境下的使用

参考 来源于黑马程序员&#xff1a; 手把手教你精通新版SpringSecurity 分布式认证概念说明 分布式认证&#xff0c;即我们常说的单点登录&#xff0c;简称SSO&#xff0c;指的是在多应用系统的项目中&#xff0c;用户只需要登录一次&#xff0c;就可以访 问所有互相信任的应…...

使用 Notepad++ 编辑显示 MarkDown

Notepad 是一款免费的开源文本编辑器&#xff0c;专为 Windows 用户设计。它是替代记事本&#xff08;Notepad&#xff09;的最佳选择之一&#xff0c;因为它功能强大且轻量级。Notepad 支持多种编程语言和文件格式&#xff0c;并可以通过插件扩展其功能。 Notepad 是一款功能…...

Spring 框架数据库操作常见问题深度剖析与解决方案

Spring 框架数据库操作常见问题深度剖析与解决方案 在 Java 开发的广阔天地中&#xff0c;Spring 框架无疑是开发者们的得力助手&#xff0c;尤其在数据库操作方面&#xff0c;它提供了丰富且强大的功能。然而&#xff0c;就像任何技术一样&#xff0c;在实际项目开发过程中&a…...

第一天:爬虫介绍

每天上午9点左右更新一到两篇文章到专栏《Python爬虫训练营》中&#xff0c;对于爬虫有兴趣的伙伴可以订阅专栏一起学习&#xff0c;完全免费。 键盘为桨&#xff0c;代码作帆。这趟为期30天左右的Python爬虫特训即将启航&#xff0c;每日解锁新海域&#xff1a;从Requests库的…...

ECP在Successfactors中paylisp越南语乱码问题

导读 pyalisp:ECP中显示工资单有两种方式&#xff0c;一种是PE51&#xff0c;一种是hrform&#xff0c;PE51就是划线的那种&#xff0c; 海外使用的比较多&#xff0c;国内基本没人使用&#xff0c;hrform就是pdf&#xff0c;可以编辑pdf&#xff0c;这个国内相对使用的人 比…...

Express 中间件分类

一、 按功能用途分类 1. 应用级中间件 这类中间件应用于整个 Express 应用程序&#xff0c;会对每个进入应用的请求进行处理。通过 app.use() 方法挂载&#xff0c;可用于执行一些全局性的任务&#xff0c;像日志记录、请求预处理、设置响应头这类操作。 const express req…...

基于Multi-Runtime的云原生多态微服务:解耦基础设施与业务逻辑的革命性实践

引言&#xff1a;当微服务遭遇复杂性爆炸 在分布式系统复杂度指数级增长的今天&#xff0c;一线开发者平均需要处理27种不同的基础设施组件配置。CNCF最新研究报告指出&#xff0c;采用Multi-Runtime架构可减少83%的非功能性代码编写量&#xff0c;同时使分布式原语&#xff0…...