osg坦克大战游戏开发过程
Posted ACskyline
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)效果展示
以上是关于osg坦克大战游戏开发过程的主要内容,如果未能解决你的问题,请参考以下文章