osg坦克大战游戏开发过程

一、3D建模

(1)实现方法

         3D建模是3D游戏开发中重要的一部分,若没有3D建模的过程而只靠osg内置的基本体,就不会有精致的模型。我们使用了Maya 2014来完成相应的建模工作,得到了我们的游戏中的主体——一辆虎式坦克。

(2)实现过程

         在建模过程中,我们主要使用了立方体模型,通过大量的挤压命令,产生棱角分明的装甲。而最为复杂的履带则是通过建立运动路径并在路径上克隆单一履带单元来完成的。

(3)效果展示

二、纹理映射

(1)实现方法

         在游戏中,我们需要将地图铺上贴图,以此来分辨角色是否在移动。为此,我们先建立了一个足够大的四边形来代表地面,并付给其一个纹理贴图。

(2)实现过程

指定一系列顶点来描述一个四边形:

         osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;

         vertices->push_back(osg::Vec3(-50.0f, -50.0f, -0.8f));

         vertices->push_back(osg::Vec3(-50.0f, 50.0f, -0.8f));

         vertices->push_back(osg::Vec3(50.0f, 50.0f, -0.8f));

         vertices->push_back(osg::Vec3(50.0f, -50.0f, -0.8f));

并指定其法向量:

         osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;

         normals->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));

随后设置纹理的坐标:

         osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;

         texcoords->push_back(osg::Vec2(0.0f, 0.0f));

         texcoords->push_back(osg::Vec2(0.0f, 1.0f));

         texcoords->push_back(osg::Vec2(1.0f, 1.0f));

         texcoords->push_back(osg::Vec2(1.0f, 0.0f));

加载纹理图片:

         osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;

         osg::ref_ptr<osg::Image> image = osgDB::readImageFile("Images/lz.rgb");

         texture->setImage(image.get());

创建一个geometry节点并将图形和纹理添加到节点:

         osg::ref_ptr<osg::Geometry> quad = new osg::Geometry;

         quad->setVertexArray(vertices.get());

         quad->setNormalArray(normals.get());

         quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));

         quad->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);//BIND_OVERALL);

         quad->setTexCoordArray(0, texcoords.get());

创建一个geode节点并将geometry节点添加到geode节点并加入场景:

         osg::ref_ptr<osg::Geode> map = new osg::Geode;

         map->addDrawable(quad.get());

         map->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get());

         root->addChild(map);

(3)效果展示

三、自动寻路

(1)实现方法

         游戏中,对方坦克的目标是击毁我方坦克,对方坦克由电脑控制,所以需要一套人工智能来决策和执行它的动作行为。我们采用最简单的状态机的方法,实现了敌方坦克向我方坦克移动以及射击的功能。为了平衡游戏难度,当敌我坦克距离拉大时,敌方的命中率会线性下降,直到阈值。

(2)实现过程

if (tank->life > 0)

         {

                   switch (state)

                   {

                   case 0://瞄准开炮状态

                            //获取炮塔旋转角度

                            if (current * 180.0f / osg::PI > accuracy + 2)//若在允许范围外

                            {

                                     //旋转炮塔

                            }

                            else//若在允许范围内

                            {

                                     now = clock();

                                     if (now - last >= fireGap){//若已过开炮间隔

                                               if (rand() % 100 < 50){//随机决定变量有1/2的几率决定开炮

                                                        //开炮

                                                        last = clock();

                                                        //转入下一个状态

                                               }

                                     }

                            }

                            break;

                   case 1://移动状态

                            switch (stage){

                            case 0://旋转车身

                                     //获取炮塔旋转角度

                                     if (current * 180.0f / osg::PI > accuracy + 1)//若在允许范围外

                                     {

                                               //旋转车身

                                     }

                                     else{//若在允许范围内

                                               //转移到下一个阶段

                                     }

                                     break;

                            case 1://移动车身

                                     //移动车身

                                     if (count >= stageCount[1]){//如果到达阶段限定的时间

                                               //转移到下个阶段

                                     }

                                     break;

                            case 2://旋转车身

                                     //获取炮塔旋转角度

                                     if (current * 180.0f / osg::PI > accuracy + 1)//若在允许范围外

                                     {

                                               //旋转车身

                                     }

                                     else{//若在允许范围内

                                               //转移到下一个阶段

                                     }

                                     break;

                            case 3://移动车身

                                     //移动车身

                                     if (count >= stageCount[3]){ //如果到达阶段限定的时间

                                               转移到瞄准开炮状态

                                     }

                                     break;

                            default:

                                     break;

                            }

                            break;

                   default:

                            break;

                   }

         }

(3)效果展示

         由于此要素无法通过截图展示,请运行程序自行观察。

四、碰撞检测

(1)实现方法

         游戏中规定,当任意一方的坦克被炮弹击中则生命值减一,当两方坦克之间发生碰撞之后双方的生命值也会减一。为了实现此项功能,需要引入碰撞检测。osg中提供了两种方法,一种是使用求交器,另一种是使用包围盒。

         求交器可以被绑定到遍历器上,以实现整个场景的求交。常用的求交器有直线求交器和多边形求交器。由于直线求交器只检测直线与多边形的碰撞事件,故多用于判断鼠标点击事件,此处若想完成既定功能需使用多边形求交器。但是多边形求交器是一组较复杂的运算规则,所以在设定初始值时需同时设定需要被检测的多边形,如果这个多边形每帧发生变化,则需要每帧都重新指定多边形求交器的参数,具体来说就是polytope类型的变量,用以指定多边形的网格,而这一过程比较耗费时间,且其位置默认为坐标原点,需自己获取其实际世界坐标。

         因此,我们使用了第二种方法,即包围盒的方法。osg中的包围盒有两种,一种是getBound()函数返回的球形包围盒,另一种是getBoundingBox()函数返回的立方体包围盒,但是只有Drawable节点才有getBoundingBox()的函数而且此包围盒,故不包含任何位置旋转等信息,相反所有Node节点都有getBound()函数,故MatrixTransform节点的getBound()函数包含位置旋转等信息。由于我们的实体节点是由MatrixTransform节点挂载的,所以想用包围盒只能选用球体包围盒。但是球体包围盒有一个缺点,就是范围不准确,可能比实际物体要大一点。

(2)实现过程

//两辆坦克的碰撞

if(tank1.transform.getMatrix()->getBound().intersects(tank2.transform.getMatrix()->getBound()))

{

                                         tank1.life--;

                                         tank2.life--;

                                         if (tank1.life <= 0){

                                                  root->removeChild(tank1.transform.getMatrix());

                                                  last1 = clock();

                                         }

                                         if (tank2.life <= 0){

                                                  root->removeChild(tank2.transform.getMatrix());

                                                  last2 = clock();

                                         }

                                }

(3)效果展示

         由于此要素无法通过截图展示,请运行程序自行观察。

五、天空盒

(1)实现方法

         天空盒可以理解为内部附有贴图的立方体,当镜头移动时,他们也会随着镜头移动,而当镜头旋转时,他们不会随着镜头旋转,从而产生图像在遥远地方的感觉且不同方向的图像是连续且不同的。另一个关键在于天空盒的深度必须是最大,这样才能不会遮挡所有其他物体。

(2)实现过程

//SkyBox类构造函数

SkyBox::SkyBox()

{

         setReferenceFrame(osg::Transform::ABSOLUTE_RF);

         setCullingActive(false);

         osg::StateSet* ss = getOrCreateStateSet();

         ss->setAttributeAndModes(new osg::Depth(         osg::Depth::LEQUAL, 1.0f, 1.0f)); //设置深度

         ss->setMode(GL_LIGHTING, osg::StateAttribute::OFF);

         ss->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);

         ss->setRenderBinDetails(5, "RenderBin"); //设置渲染顺序

}

 

//设置各方向贴图

void SkyBox::setEnvironmentMap(unsigned int unit,

         osg::Image* posX, osg::Image* negX,

         osg::Image* posY, osg::Image* negY,

         osg::Image* posZ, osg::Image* negZ)

{

         osg::ref_ptr<osg::TextureCubeMap> cubemap =

                   new osg::TextureCubeMap;

         cubemap->setImage(osg::TextureCubeMap::POSITIVE_X, posX);

         cubemap->setImage(osg::TextureCubeMap::NEGATIVE_X, negX);

         cubemap->setImage(osg::TextureCubeMap::POSITIVE_Y, posY);

         cubemap->setImage(osg::TextureCubeMap::NEGATIVE_Y, negY);

         cubemap->setImage(osg::TextureCubeMap::POSITIVE_Z, posZ);

         cubemap->setImage(osg::TextureCubeMap::NEGATIVE_Z, negZ);

         cubemap->setResizeNonPowerOfTwoHint(false);

         getOrCreateStateSet()->setTextureAttributeAndModes(unit, cubemap.get());

}

 

//加载天空盒的几何形状

osg::ref_ptr<osg::Geode> geode = new osg::Geode;

geode->addDrawable( new osg::ShapeDrawable(

new osg::Sphere(osg::Vec3(), scene->getBound().radius())) );

 

//创建天空盒类的指针并设置

osg::ref_ptr<SkyBox> skybox = new SkyBox;

skybox->getOrCreateStateSet()->setTextureAttributeAndModes(0, new osg::TexGen );

skybox->setEnvironmentMap( 0,

osgDB::readImageFile("Cubemap_snow/posx.jpg"),

osgDB::readImageFile("Cubemap_snow/negx.jpg"),

osgDB::readImageFile("Cubemap_snow/posy.jpg"),

osgDB::readImageFile("Cubemap_snow/negy.jpg"),

osgDB::readImageFile("Cubemap_snow/posz.jpg"),

osgDB::readImageFile("Cubemap_snow/negz.jpg") );

skybox->addChild( geode.get() );

(3)效果展示

六、Shader

(1)实现方法

         由于使用Maya建模时模型是带有材质及贴图的,所以导出的obj文件也是有材质和贴图信息的。但是由于材质和贴图原路径地址以及文件大小的原因,我们没有将这些材质和贴图添加入游戏中。替代的方法是运用shader直接将模型的表面片元加上类似卡通渲染的风格,这样不但解决了材质的问题还不会使模型过于真实而感觉突兀。

         具体实现时,我们创建了一个program变量,为其添加了一个顶点着色器,一个片元着色器,并在主体模型的节点的stateset中添加了该program,并指定了uniform变量。

         随后,我们又添加了实时更换着色器颜色的功能。我们本想通过随时改变uniform变量来达到这点,但是发现对于uniform指针,我们无法直接改变指针的内容而不改变指针地址(无法直接声明变量必须使用new来创建),所以指定了新的uniform之后必须重新绑定新的uniform的指针。

(2)实现过程

//着色器的代码

static const char* vertSource = {//顶点着色器

         "varying vec3 normal;\n"

         "void main()\n"

         "{\n"

         " normal = normalize(gl_NormalMatrix * gl_Normal);\n"//指定法线

         " gl_Position = ftransform();\n"

         "}\n"

};

 

static const char* fragSource = {//片元着色器

         "uniform vec4 color1;\n"

         "uniform vec4 color2;\n"

         "uniform vec4 color3;\n"

         "uniform vec4 color4;\n"

         "varying vec3 normal;\n"

         "void main()\n"

         "{\n"

         //默认光源位置点成片元法线,计算夹角

         " float intensity = dot(vec3(gl_LightSource[0].position),normal); \n"

         " if (intensity > 0.95) gl_FragColor = color1;\n"//高亮区域赋值color1

         " else if (intensity > 0.5) gl_FragColor = color2;\n"//中亮区域赋值color2

         " else if (intensity > 0.25) gl_FragColor = color3;\n"//低亮区域赋值color3

         " else gl_FragColor = color4;\n"//其余区域赋值color4

         "}\n"

};

 

//着色器的绑定

         osg::ref_ptr<osg::Shader> vertShader

                   = new osg::Shader(osg::Shader::VERTEX, vertSource);//获取GLSL代码

         osg::ref_ptr<osg::Shader> fragShader

                   = new osg::Shader(osg::Shader::FRAGMENT, fragSource); //获取GLSL代码

         osg::ref_ptr<osg::Program> program = new osg::Program;//创建stateset的设置参数

         program->addShader(vertShader.get());//添加着色器

         program->addShader(fragShader.get());//添加着色器

         osg::StateSet * ss = body.getMatrix()->getOrCreateStateSet();//获取节点的stateset

         ss->setAttributeAndModes(program.get());//绑定stateset的设置参数

         ss->addUniform(new osg::Uniform//添加Uniform变量

                                                        ("color1", osg::Vec4(color.r(), color.g(), color.b(), 1.0)));

         ss->addUniform(new osg::Uniform//添加Uniform变量

                                                        ("color2", osg::Vec4(color.r() / 2, color.g() / 2, color.b() / 2, 1.0f)));

         ss->addUniform(new osg::Uniform//添加Uniform变量

                                                        ("color3", osg::Vec4(color.r() / 4, color.g() / 4, color.b() / 4, 1.0f)));

         ss->addUniform(new osg::Uniform//添加Uniform变量

                                                        ("color4", osg::Vec4(color.r() / 8, color.g() / 8, color.b() / 8, 1.0f)));

(3)效果展示

转载于:https://www.cnblogs.com/ACskyline/p/5603377.html

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本人主要从事图形图象工作,空闲之余接触了一些游戏编程,特写一些编程心得,本文 适合没有接触过人物动画编程的初学者,希望游戏制作的大虾们指点,交流。 在以前还有没接触人物动画编程的时候,觉得通过编程让人物动起来一定是一件很麻烦 的事情,尤其是初学者,大都会摸不着头脑,碰到诸如骨骼动画之类,似乎无从下手。但是 当你了解了它们的格式,就会发现其实真正的人物动画的制作并不是在编程阶段,而是在模 型构建阶段,程序员主要做工作的是掌握模型文件的格式,将存储在人物模型中的各种信息, 如顶点,面片,材质,骨骼或顶点运动的关键帧序列等信息读入内存然后用你熟悉的 SDK 绘制出来,再根据时间采用线性或者球形插值对动作序列的关键帧进行插值,不断变换顶点 坐标,从而得到了一系列连续的人物动画,听起来确实不难吧!当然你也可以在程序里自己 控制人物每一帧每一个关节的运动,我想做游戏的很少有人这么做吧。下面我向大家介绍一 下自己是如何编写人物动画程序的。本人从事的图形图象开发主要是基于 OpenGL 和 OSG 因此范例程序将采用 OpenGL 或 OSG。先声明一下,本人的语言表达能力很差,请大家多 多谅解指正。 考虑到没有接触过人物模型的朋友,我首先从人物模型的结构讲起,游戏人物编程主要 采用的人物模型格式有 Quake 里的md2,md3,Half Life 里的 mdl,Doom里的 md5,还有 典型的骨骼动画模型 ms3d…,至于3dmax 的模型,本人觉得太麻烦!在此我说两个有代表 性的 Md3,和 ms3d,其它的模型都大同小异,只要你了解了它们的格式,程序实现都不难。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值