本节课实现了一个填满格子的小游戏,有点类似于小时候玩的吃豆子的游戏。本课并没有使用什么新的技术,但是实现的过程比较繁琐,不喜欢深入细节的读者可以跳过本课。
首先我们需要创建文字,下面的代码是其中一些文字的创建方式,其他的与之类似:
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]; }
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; }
camera->setProjectionMatrixAsOrtho(0, traits->width, traits->height, 0, -1.0, 1.0);