转载请注明出处:http://blog.youkuaiyun.com/pizzazhang
源码和可执行程序链接http://code.google.com/p/pizzaprojects/downloads/list
这篇文章将使用Ogre Wiki上Base框架和自己的GUI框架来做一个NPC对话的演示例子。
主要实现的效果是: GUI窗口的响应、GUI窗口动画、Ogre轨迹动画实现镜头的转移。
先看下效果图(因为是转换的GIF,比较模糊, 可惜这里不支持插入视频 = =!)
可以看到, 当鼠标点击NPC(兽头 = =!)后,镜头便会拉近,然后弹出一个对话窗口, 对话文本缓缓出现,退出游戏的时候(ESC),会有一个退出菜单缓缓降下。
下面就来实现这个demo。
首先我们需要BaseApplication.h和BaseApplication.cpp 以及 MyGUISystem.h、 MyGUISystem.cpp
在一个新文件,比如Demo.cpp中 我们来使用这2个框架。
先贴上代码,然后再解释关键部分。
Demo.cpp
- // [4/4/2011 Pizza]
- #include "BaseApplication.h"
- #include "MyGUISystem.h"
- using namespace Ogre;
- class Demo : public BaseApplication
- {
- enum QueryMask
- {
- QUERY_MASK = 1<<0, // 可查询
- HIDE_MASK = 1<<1 // 不可查询
- };
- public:
- Demo()
- :mMouseLeftClicked(false), mStartTrack(false), mRaySceneQuery(0), mExitUI(false)
- {}
- virtual ~Demo()
- {
- if(mRaySceneQuery)
- {
- mSceneMgr->destroyQuery(mRaySceneQuery);
- }
- }
- bool frameRenderingQueued(const Ogre::FrameEvent& evt)
- {
- if(mShutDown)
- return false;
- if(mStartTrack)
- {
- mAnimState->addTime(evt.timeSinceLastFrame);
- }
- MyGUISystem::getSingletonPtr()->update(evt.timeSinceLastFrame);
- return BaseApplication::frameRenderingQueued(evt);
- }
- void createScene()
- {
- //创建射线查询
- mRaySceneQuery = mSceneMgr->createRayQuery(Ray());
- //设置GUI
- setupGUI();
- //灯光设置
- setupLight();
- //天空盒
- mSceneMgr->setSkyBox(true, "Examples/MorningSkyBox");
- //来个标志性的兽头演示
- Entity* head = mSceneMgr->createEntity("Head", "ogrehead.mesh");
- mCurrentNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
- mCurrentNode->attachObject(head);
- head->setQueryFlags(QUERY_MASK);
- mCamNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
- mCamNode->attachObject(mCamera);
- mCamNode->setPosition(0, 0, 230);
- mCamera->setQueryFlags(HIDE_MASK);
- createTrackAnimation();
- }
- void setupGUI()
- {
- MyGUISystem::getSingletonPtr()->init();
- MyGUISystem::getSingletonPtr()->loadLayout("DialogWindow");
- MyGUISystem::getSingletonPtr()->loadLayout("Root");
- //设置图片资源
- MyGUISystem::createImageset("bgImg", "bg.jpg");
- MyGUISystem::createImageset("npcpic", "npcpic.png");
- //设置图片属性,并添加给Static Image类型的窗口
- MyGUISystem::setProperty("Root/BgImg", "Image", "set:bgImg image:full_image");
- MyGUISystem::setProperty("DialogWindow/NPCPic", "Image", "set:npcpic image:full_image");
- //一开始隐藏ESCAPE的UI
- MyGUISystem::getSingletonPtr()->getWindow("Root")->hide();
- //对话框的设置
- CEGUI::MultiLineEditbox* dialogbox = static_cast<CEGUI::MultiLineEditbox*>(MyGUISystem::getSingletonPtr()
- ->getWindow("DialogWindow/DialogBgImg/DilogText"));
- //只读,无法编辑
- dialogbox->setReadOnly(true);
- dialogbox->setWordWrapping(true);
- CEGUI::String str = (CEGUI::utf8*)Ogre::UTFString(L"你好,我的朋友!我是可爱的有着绿色环保肤色的Ogre先生,有什么可以帮忙的吗?").asUTF8_c_str();
- dialogbox->setText(str);
- //从XML载入动画
- CEGUI::AnimationManager::getSingleton().loadAnimationsFromXML("ExitUI_Anim.xml");
- CEGUI::AnimationManager::getSingleton().loadAnimationsFromXML("Dialog_Anim.xml");
- //GUI事件
- createGUIEvent();
- }
- void startGUIAnim(const std::string& animName, const std::string& targetWindow)
- {
- mGUIAnim = CEGUI::AnimationManager::getSingleton().getAnimation(animName);
- //实例化动画
- mGUIAnimInst = CEGUI::AnimationManager::getSingleton().instantiateAnimation(mGUIAnim);
- //动画的应用窗口
- mGUIAnimInst->setTargetWindow(MyGUISystem::getSingletonPtr()->getWindow(targetWindow));
- //动画开始
- mGUIAnimInst->start();
- }
- void createGUIEvent()
- {
- //注册事件
- MyGUISystem::subscribeEvent("Root/BgImg/QuitBtn", CEGUI::PushButton::EventClicked,
- CEGUI::Event::Subscriber(&Demo::quit, this));
- MyGUISystem::subscribeEvent("DialogWindow/DialogBgImg/OkBtn", CEGUI::PushButton::EventClicked,
- CEGUI::Event::Subscriber(&Demo::know, this));
- }
- //设置灯光
- void setupLight()
- {
- mSceneMgr->setAmbientLight(ColourValue(0.3, 0.3, 0.3));
- mSceneMgr->createLight()->setPosition(20, 80, 50);
- }
- // 创建轨迹动画
- void createTrackAnimation()
- {
- //设置3秒的动画
- Animation* anim = mSceneMgr->createAnimation("CameraTrack", 3);
- //设置动画的Translation变化模式为样条线变化
- anim->setInterpolationMode(Animation::IM_SPLINE);
- // 创建节点轨迹动画
- NodeAnimationTrack* track = anim->createNodeTrack(0, mCamNode);
- // 关键帧信息
- track->createNodeKeyFrame(0)->setTranslate(Vector3(0, 0, 230));
- track->createNodeKeyFrame(1)->setTranslate(Vector3(-15, 0, 120));
- track->createNodeKeyFrame(2)->setTranslate(Vector3(-20, 0, 90));
- track->createNodeKeyFrame(3)->setTranslate(Vector3(-22, 0, 70));
- mAnimState = mSceneMgr->createAnimationState("CameraTrack");
- }
- /*
- *开始轨迹动画
- params
- @trackNode 动画跟踪节点
- @start 是否开始动画
- @loop 动画是否循环
- */
- void startTrackAnimation(SceneNode* trackNode, bool start, bool loop = false)
- {
- //设置摄像机的自动跟踪节点
- mCamera->setAutoTracking(start, trackNode);
- // 设置动画状态
- mAnimState->setTimePosition(0);
- mAnimState->setEnabled(start);
- mAnimState->setLoop(loop);
- }
- /*
- * 键盘鼠标事件
- */
- bool mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
- {
- MyGUISystem::getSingletonPtr()->injectMouseButtonDown(MyGUISystem::convertButton(id));
- if(id == OIS::MB_Left)
- {
- if(!mStartTrack && !mExitUI)
- {
- mMouseLeftClicked = true;
- CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
- Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width),
- mousePos.d_y/float(arg.state.height));
- mRaySceneQuery->setRay(mouseRay);
- //按深度排序,得到最近的物体
- mRaySceneQuery->setSortByDistance(true);
- mRaySceneQuery->setQueryMask(QUERY_MASK);
- RaySceneQueryResult& result = mRaySceneQuery->execute();
- RaySceneQueryResult::iterator itr = result.begin();
- if(itr != result.end() && itr->movable)
- {
- mCurrentNode = itr->movable->getParentSceneNode();
- mCurrentNode->showBoundingBox(true);
- //开始轨迹动画
- mStartTrack = true;
- startTrackAnimation(mCurrentNode, true);
- startGUIAnim("DialogAnim", "DialogWindow/DialogBgImg/DilogText");
- MyGUISystem::getSingletonPtr()->loadLayout("DialogWindow");
- }
- else
- {
- mCurrentNode->showBoundingBox(false);
- mCamera->setAutoTracking(false);
- }
- }
- }
- return true;
- }
- bool mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
- {
- MyGUISystem::getSingletonPtr()->injectMouseButtonUp(MyGUISystem::convertButton(id));
- if(id == OIS::MB_Left)
- mMouseLeftClicked = false;
- return true;
- }
- bool mouseMoved( const OIS::MouseEvent &arg )
- {
- MyGUISystem::getSingletonPtr()->injectMouseMove(arg.state.X.rel, arg.state.Y.rel);
- return true;
- }
- bool keyPressed( const OIS::KeyEvent &arg )
- {
- if(arg.key == OIS::KC_ESCAPE)
- {
- mExitUI = !mExitUI;
- if(mExitUI && !mStartTrack)
- {
- startGUIAnim("ExitUIAnim", "Root/BgImg");
- MyGUISystem::getSingletonPtr()->loadLayout("Root");
- }
- else
- {
- MyGUISystem::getSingletonPtr()->getWindow("Root")->hide();
- }
- }
- return true;
- }
- private:
- bool quit(const CEGUI::EventArgs& e)
- {
- mShutDown = true;
- return true;
- }
- bool know(const CEGUI::EventArgs& e)
- {
- MyGUISystem::getSingletonPtr()->getWindow("DialogWindow")->hide();
- mStartTrack = false;
- mExitUI = false;
- return true;
- }
- private:
- bool mMouseLeftClicked; //鼠标是否左击
- RaySceneQuery* mRaySceneQuery; //射线查询
- SceneNode* mCurrentNode; //当前点击节点
- AnimationState* mAnimState; //Ogre实体动画状态
- CEGUI::Animation* mGUIAnim; //GUI动画
- CEGUI::AnimationInstance* mGUIAnimInst; //GUI动画实例
- bool mStartTrack; //是否开始轨迹动画
- SceneNode* mCamNode; //摄像机节点
- bool mExitUI; //是否是退出UI
- };
- INT WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, INT)
- {
- Demo demo;
- try
- {
- demo.go();
- }
- catch(Exception& e)
- {
- MessageBox(NULL, e.getFullDescription().c_str(), "Ogre Exception", MB_OK);
- }
- }
首先,这个轨迹动画的实现, 步骤为:
mSceneMgr->createAnimation 创建动画
setInterpolationMode(Animation::IM_SPLINE)设置动画移动变换的插值模式: IM_SPLINE是样条线插值,就是使用一条不规则的样条线定义一些列关键帧。
anim->createNodeTrack(0, mCamNode) 创建节点轨迹,第二个参数指定轨迹运动的节点,这里需要让镜头移动,所以绑定镜头节点。
createNodeKeyFrame(0)->setTranslate 接下来就是设置关键帧信息了。 每个关键帧都使用的是移动的变换。
最后创建动画状态,createAnimationState, 我们使用动画的时候,都是引用这个动画状态来实现动画。
动画的使用很简单:// 设置动画状态
mAnimState->setTimePosition(0);
mAnimState->setEnabled(start);
mAnimState->setLoop(loop);
第一句设置当前的时间轴为0, 就是动画从头开始。
接下来我们需要实现点击NPC后才开始轨迹动画。 所以这里使用了一个射线查询来得到点击的物体。 注意需要使用一个遮罩把摄像机设置为不查询, 否则很可能第一个查询的总是摄像机而不是我们想要的物体。
if(itr != result.end() && itr->movable)
{
mCurrentNode = itr->movable->getParentSceneNode();
mCurrentNode->showBoundingBox(true);
//开始轨迹动画
mStartTrack = true;
startTrackAnimation(mCurrentNode, true);
startGUIAnim("DialogAnim", "DialogWindow/DialogBgImg/DilogText");
MyGUISystem::getSingletonPtr()->loadLayout("DialogWindow");
}
以上代码基本就是查询到物体后, 显示包围框,然后开始镜头的运动并弹出对话窗口。
接下来是GUI的动画系统。在CEGUI中使用动画其实也很简单, 在可以定位到的资源路径下新建你的动画脚本, 比如我定义一个对话框的动画效果, 我就新建一个Dialog_Anim.xml
然后写上:
- <Animations>
- <AnimationDefinition name="DialogAnim" duration="2" replayMode="once">
- <Affector property="Alpha" interpolator="float">
- <KeyFrame position="0" value="0.5" />
- <KeyFrame position="2" value="1.0" />
- </Affector>
- <Affector property="UnifiedAreaRect" interpolator="URect">
- <KeyFrame position="0" value="{{0.1,0},{1,0},{0.9,0},{1.4,0}}" />
- <KeyFrame position="2" value="{{0.1,0},{0.25,0},{0.9,0},{0.65,0}}" />
- </Affector>
- </AnimationDefinition>
- </Animations>
基本就是一个效果器和关键帧的设置。效果器里的property可以是CEGUI窗口的任意属性。 可以查阅CEGUI的API获取。
以上动画的效果是2秒内某个窗口逐渐出现并且从下面看不见的地方逐渐上升到合适的位置。 该动画播放一次(once)。
在代码中,这样使用动画:
//从XML载入动画
CEGUI::AnimationManager::getSingleton().loadAnimationsFromXML("ExitUI_Anim.xml");
CEGUI::AnimationManager::getSingleton().loadAnimationsFromXML("Dialog_Anim.xml");
然后在需要调用的地方开始动画:
mGUIAnim = CEGUI::AnimationManager::getSingleton().getAnimation(animName);
//实例化动画
mGUIAnimInst = CEGUI::AnimationManager::getSingleton().instantiateAnimation(mGUIAnim);
//动画的应用窗口
mGUIAnimInst->setTargetWindow(MyGUISystem::getSingletonPtr()->getWindow(targetWindow));
//动画开始
mGUIAnimInst->start();