一、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)效果展示



