用OpenSceneGraph实现的NeHe OpenGL教程 - 第二十一课

  • 简介

本节课实现了一个填满格子的小游戏,有点类似于小时候玩的吃豆子的游戏。本课并没有使用什么新的技术,但是实现的过程比较繁琐,不喜欢深入细节的读者可以跳过本课。

  • 实现

首先我们需要创建文字,下面的代码是其中一些文字的创建方式,其他的与之类似:

osg::Group* createGridCrazyText()
{
	osg::Group *fontGroup = new osg::Group;
	osg::MatrixTransform *mt = new osg::MatrixTransform;
	mt->setMatrix(osg::Matrix::translate(207,24,0));
	osg::Geode *font = new osg::Geode;

	osgText::Text *text = new osgText::Text;
	text->setText("GRID CRAZY");
	text->setCharacterSize(20.0f);
	text->setFont("fonts/arial.ttf");
	text->setColor(osg::Vec4(1.0f,0.5f,1.0f, 1.0f));
	font->addDrawable(text);
	
	mt->addChild(font);
	fontGroup->addChild(mt);
	return fontGroup;
}
接着创建玩家的生命显示,在右上角处

osg::Switch*		createLives()
{
	osg::Switch *livesGroup = new osg::Switch;
	g_livesGroup = livesGroup;

	for (int i = 0; i < lives-1; ++i)
	{
		osg::MatrixTransform *transMT = new osg::MatrixTransform;
		transMT->setMatrix(osg::Matrix::translate(490+(i*40.0f),40.0f,0.0f));
		osg::MatrixTransform *rotMT = new osg::MatrixTransform;
		rotMT->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::Z_AXIS, 3.5));

		osg::Group *live = new osg::Group;

		osg::Geode *liveGeode1 = new osg::Geode;
		osg::Geometry *lineGeometry1 = new osg::Geometry;
		osg::Vec2Array *verts1 = new osg::Vec2Array;
		verts1->push_back(osg::Vec2(-5,-5));
		verts1->push_back(osg::Vec2(5,5));
		verts1->push_back(osg::Vec2(5,-5));
		verts1->push_back(osg::Vec2(-5,5));
		osg::Vec3Array *colors1 = new osg::Vec3Array;
		colors1->push_back(osg::Vec3(0, 1, 0));
		lineGeometry1->setVertexArray(verts1);
		lineGeometry1->setColorArray(colors1, osg::Array::BIND_OVERALL);
		lineGeometry1->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, 2));
		lineGeometry1->getOrCreateStateSet()->setMode(GL_LIGHTING, false);
		liveGeode1->addDrawable(lineGeometry1);

		osg::MatrixTransform *crossMT = new osg::MatrixTransform;
		crossMT->addUpdateCallback(new osg::AnimationPathCallback(osg::Vec3(), osg::Z_AXIS, 7.0));

		osg::Geode *liveGeode2 = new osg::Geode;
		osg::Geometry *lineGeometry2 = new osg::Geometry;
		osg::Vec2Array *verts2 = new osg::Vec2Array;
		verts2->push_back(osg::Vec2(-7,0));
		verts2->push_back(osg::Vec2(7,0));
		verts2->push_back(osg::Vec2(0,-7));
		verts2->push_back(osg::Vec2(0,7));
		osg::Vec3Array *colors2 = new osg::Vec3Array;
		colors2->push_back(osg::Vec3(0.0f,0.75f,0.0f));
		lineGeometry2->setVertexArray(verts2);
		lineGeometry2->setColorArray(colors2, osg::Array::BIND_OVERALL);
		lineGeometry2->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, 2));
		lineGeometry2->getOrCreateStateSet()->setMode(GL_LIGHTING, false);
		liveGeode2->addDrawable(lineGeometry2);
		
		live->addChild(liveGeode1);
		live->addChild(crossMT);
		crossMT->addChild(liveGeode2);
		transMT->addChild(rotMT);
		rotMT->addChild(live);
		livesGroup->addChild(transMT);
	}
	
	return livesGroup;
}
使用Swith节点的原因是当生命减少的时候我们关掉其中的子节点。

接着要创建网格和填充网格的四边形:

osg::Group*		createGrid()
{
	osg::Group *group = new osg::Group;
	osg::Geode *geode = new osg::Geode;

	for (int i = 0; i < 11; i++)
	{
		for (int j = 0; j < 11; j++)
		{
			if (i < 10)
			{
				osg::Geometry *line = new osg::Geometry;
				osg::Vec2dArray *vertices = new osg::Vec2dArray;
				vertices->push_back(osg::Vec2d(TX(20+i*60), TY(70+j*40)));
				vertices->push_back(osg::Vec2d(TX(80+i*60), TY(70+j*40)));
				osg::Vec3Array *colors = new osg::Vec3Array;
				colors->push_back(osg::Vec3(0.0, 0.5f, 1.0f));
				line->setVertexArray(vertices);
				line->setColorArray(colors, osg::Array::BIND_OVERALL);
				line->getOrCreateStateSet()->setMode(GL_LIGHTING, false);
				osg::BlendFunc *blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				line->getOrCreateStateSet()->setAttributeAndModes(blendFunc);
				line->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, 2));
				line->setUpdateCallback(new HColorUpdateCallback(i, j));
				geode->addDrawable(line);
			}

			if (j < 10)
			{
				osg::Geometry *vlineGeometry = new osg::Geometry;
				osg::Vec2dArray *vlinevertices = new osg::Vec2dArray;
				vlinevertices->push_back(osg::Vec2d(TX(20+i*60), TY(70+j*40)));
				vlinevertices->push_back(osg::Vec2d(TX(20+i*60), TY(110+j*40)));
				osg::Vec3Array *vlinecolors = new osg::Vec3Array;
				vlinecolors->push_back(osg::Vec3(0.0, 0.5f, 1.0f));
				vlineGeometry->setVertexArray(vlinevertices);
				vlineGeometry->setColorArray(vlinecolors, osg::Array::BIND_OVERALL);
				vlineGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, false);
				osg::BlendFunc *blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				vlineGeometry->getOrCreateStateSet()->setAttributeAndModes(blendFunc);
				vlineGeometry->setUpdateCallback(new VColorUpdateCallback(i, j));
				vlineGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, 2));
				geode->addDrawable(vlineGeometry);
			}
		}
	}
	group->addChild(geode);
	return group;
}
NeHe教程定义了这样一个变量
struct	object												
{
	int		fx, fy;
	int		x, y;
	float	spin;
};
其中的fx和fy代表了横竖网格的交点,我们玩家角色会一直在交点上出现,实现这个效果需要我们一直调整玩家角色的位置,代码如下:

			if (player.fx<player.x*60)
			{
				player.fx+=steps[adjust];	
			}
			if (player.fx>player.x*60)
			{
				player.fx-=steps[adjust];
			}
			if (player.fy<player.y*40)
			{
				player.fy+=steps[adjust];
			}
			if (player.fy>player.y*40)
			{
				player.fy-=steps[adjust];
			}

这部分代码放在交互的GUIEventHandler的Frame时间中:

case (osgGA::GUIEventAdapter::FRAME):
使用上下左右键操作角色的代码如下;

		switch(ea.getEventType())
		{
		case (osgGA::GUIEventAdapter::KEYDOWN):
			{
				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Left)
				{
					if((player.x>0) && (player.fx==player.x*60) && (player.fy==player.y*40))
					{
						player.x--;
						hline[player.x][player.y]=true;
					}
				}

				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Right)
				{
					if ((player.x<10) && (player.fx==player.x*60) && (player.fy==player.y*40))
					{
						hline[player.x][player.y]=true;
						player.x++;
					}
				}

				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Down)
				{
					if((player.y<10) && (player.fx==player.x*60) && (player.fy==player.y*40))
					{
						vline[player.x][player.y]=true;
						player.y++;	
						
					}
				}

				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Up)
				{
					if((player.y>0) && (player.fx==player.x*60) && (player.fy==player.y*40))
					{									
						player.y--;
						vline[player.x][player.y]=true;
					}
				}

				if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Space)
				{
					if (gameover)
					{
						gameover=false;
						filled=true;
						level=1;
						level2=1;
						stage=0;
						lives=5;
						g_livesGroup->setAllChildrenOn();
						g_gameoverSwitch->setAllChildrenOff();
					}
				}
			}	
		default: break;
		}
		return false;
	}
可以看到只有到在帧循环中会不断调整角色的位置至交点处

通过交互修改了角色的位置变量,在更新回调中需要更新角色位置,代码如下:

class TranslatePlayerCallback : public osg::NodeCallback
{
public:
	virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
	{
		osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(node);
		if (!mt)
		{
			return;
		}
		mt->setMatrix(osg::Matrix::translate((player.fx+20.0f),(player.fy+70.0f),0.0f));

		traverse(node, nv);
	}
};
其他更新回调包括字体的闪烁效果:(Game OVer),敌人的位置等都是这样实现的

class FontBlickCallback : public osg::Drawable::UpdateCallback
{
public:

	virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)
	{
		osgText::Text *text = dynamic_cast<osgText::Text*>(drawable);
		if (!text)
			return;
		text->setColor(osg::Vec4(rand() % 10 * 0.1, rand() % 10 * 0.1, rand() % 10 * 0.1, 1.0));
	}
};

最后把所有这些节点添加到场景中:

osg::Node*	buildScene()
{
	osg::Group *root = new osg::Group;

	root->addChild(createGridCrazyText());
	root->addChild(createLevelText());
	root->addChild(createStageText());
	root->addChild(createGameOverScene());
	root->addChild(createLives());
	root->addChild(createGrid());
	root->addChild(createPlayer());
	root->addChild(createHourGlasses());
	root->addChild(createEnemy());
	root->addChild(createGridQuads());
	return root;
}

需要注意的是本节课使用的投影是(0,0)点在左上角,使用

camera->setProjectionMatrixAsOrtho(0, traits->width,  traits->height, 0, -1.0, 1.0);




你可能感兴趣的:(C++,qt,OpenGL,nehe,OSG)