用OGRE1.74搭建游戏框架(一)
新版的OGRE出来了,不知什么原因抛弃了CEGUI。国内的教程好像也更新比较少了,在官网上还是发现不少资料的,现在参考官网上的一些资料来搭建一个游戏的框架。
参考的资料:
http://www.ogre3d.org/tikiwiki/Advanced+Ogre+Framework&structure=Tutorials
首先要先一个能启动OGRE的类(OgreFramework),把OGRE的初始化的相关工作放入其中,大体有如下几个东西:
- Root
- RenderWindow
- Viewport
- Log
- Timer
- InputManager / Keyboard / Mouse
- SDKTrays Manager
OgreFramework.h
/********************************************************************
created: 2012/02/25
created: 25:2:2012 11:31
filename: E:\Ogre\Project\Ogre_Project\Game_Demo\OgreFramework.h
file path: E:\Ogre\Project\Ogre_Project\Game_Demo
file base: OgreFramework
file ext: h
author: Star
purpose: ogre framework
*********************************************************************/
#ifndef _ZH_OGREFRAMEWORK_H_
#define _ZH_OGREFRAMEWORK_H_
#pragma once
#include <Ogre.h>
#include <ois/OIS.h>
#include <SdkTrays.h>
class OgreFramework: public Ogre::Singleton<OgreFramework>, OIS::KeyListener, OIS::MouseListener
{
public:
OgreFramework();
~OgreFramework();
bool InitOgre(Ogre::String wndTitle, OIS::KeyListener* pKeyListener = 0, OIS::MouseListener* pMouseListener = 0);
void UpdateOgre(double timeSinceLastFrame);
// for key event
bool keyPressed(const OIS::KeyEvent& evt);
bool keyReleased(const OIS::KeyEvent& evt);
// for mouse event
bool mouseMoved(const OIS::MouseEvent& evt);
bool mousePressed(const OIS::MouseEvent& evt, OIS::MouseButtonID id);
bool mouseReleased(const OIS::MouseEvent& evt, OIS::MouseButtonID id);
public:
Ogre::Root* m_pRoot;
Ogre::RenderWindow* m_pRenderWnd;
Ogre::Viewport* m_pViewport;
Ogre::Log* m_pLog;
Ogre::Timer* m_pTimer;
OIS::InputManager* m_pInputMgr;
OIS::Keyboard* m_pKeyboard;
OIS::Mouse* m_pMouse;
OgreBites::SdkTrayManager* m_pTrayMgr;
private:
OgreFramework(const OgreFramework&);
OgreFramework& operator= (const OgreFramework&);
};
#endif //_ZH_OGREFRAMEWORK_H_
主要还是要看InitOgre的实现:
bool OgreFramework::InitOgre(Ogre::String wndTitle, OIS::KeyListener* pKeyListener /* = 0 */, OIS::MouseListener* pMouseListener /* = 0 */)
{
Ogre::LogManager* logMgr = new Ogre::LogManager();
m_pLog = Ogre::LogManager::getSingleton().createLog("OgreLogfile.log", true, true, flase);
m_pLog->setDebugOutputEnabled(true);
m_pRoot = new Ogre::Root();
if (!m_pRoot->showConfigDialog())
return false;
m_pRenderWnd = m_pRoot->initialise(true, wndTitle);
m_pViewport = m_pRenderWnd->addViewport(0);
m_pViewport->setBackgroundColour(ColourValue(0.5f, 0.5f, 0.5f, 1.0f));
m_pViewport->setCamera(0);
size_t hWnd = 0;
OIS::ParamList paramList;
m_pRenderWnd->getCustomAttribute("WINDOW", &hWnd);
paramList.insert(OIS::ParamList::value_type("WINDOW", Ogre::StringConverter::toSgring(hWnd)));
m_pInputMgr = OIS::InputManager::createInputSystem(paramList);
m_pKeyboard = static_cast<OIS::Keyboard*>(m_pInputMgr->createInputObject(OIS::OISKeyboard, true));
m_pMouse = static_cast<OIS::Mouse*>(m_pInputMgr->createInputObject(OIS::OISMouse, true));
m_pMouse->getMouseState().height = m_pRenderWnd->getHeight();
m_pMouse->getMouseState().width = m_pRenderWnd->getWidth();
if (pKeyListener == NULL)
m_pKeyboard->setEventCallback(this);
else
m_pKeyboard->setEventCallback(pKeyListener);
if (pMouseListener == NULL)
m_pMouse->setEventCallback(this);
else
m_pMouse->setEventCallback(pMouseListener);
// load resources
Ogre::String secName, typeName, archName;
Ogre::ConfigFile cf;
cf.load("resources.cfg");
Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
while (seci.hasMoreElements)
{
secName = seci.peekNextKey();
Ogre::ConfigFile::SettingsMultiMap* settings = seci.getNext();
Ogre::ConfigFile::SettingsMultiMap::iterator i;
for (i = settings->begin(); i != settings->end(); ++i)
{
typeName = i->first;
archName = i->second;
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);
}
}
Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);
Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
m_pTrayMgr = new OgreBites::SdkTrayManager("OFTrayMgr", m_pRenderWnd, m_pMouse, 0);
m_pTimer = new Ogre::Timer();
m_pTimer->reset();
m_pRenderWnd->setActive(true);
return true;
}
里边主要做了以下几个工作:
1.创建日志管理
2.创建OGRE的Root
3.创建渲染窗口(RenderWindow)和视口(Viewport)
4.启动OIS
5.设置鼠标和键盘的监听
6.载入资源
7.设置Timer
8.初始化SDKTrayManager
9.创建并显示debug overlay
注意,这里边没有像机和场景管理器,这个要到后面的状态类中才用到。
void OgreFramework::UpdateOgre(double timeSinceLastFrame)
{
}
这个函数用于在每一帧中更新和OGRE相关的一些东西,自己写的东西是要在每个状态中自己更新。
其实游戏的框架,普遍还是用的State这个设计模式。这样就可以在开始菜单,游戏画面,结束画面中自由的切换。看Android的libgdx引擎也是这样做的,分成不同的Screen来相互切换。
用OGRE1.74搭建游戏框架(二)--添加菜单
在上一节中,已经把OGRE的一个框架搭出来了,而且用的是一个单例模式,这样就意味着我们在后面的状态模式中可以很容易的就使用OGRE来帮我们进行图形绘制。
首先,写一个所有游戏状态的基类,因为我们大体可以抽象出所有游戏状态都应该有:
1.进入这个状态时应该做一些准备工作
2.退出这个状态时应该删除某些东西
3.暂停和继续时的一些动作。
4.更新
而一个游戏里有很多状态的切换,而我们又不想让一个状态知道另一个状态的存在,于是我们就要有一个统一管理这些状态的一个类,这样就能让这些状态之间解耦。(如果不这样做,比如说我想从菜单状态切换到游戏开始状态,那么我得在开始菜单中保存一个指向游戏开始状态的指针,而游戏开始状态想切到其它的状态也得这么做。)
因此我们写一个状态管理类,它实现一个状态监听接口。
状态管理用一个栈来存储所有的状态,当从当前状态切换到另一个状态时,就把栈顶的状态取出,并调用它的Exit()函数,并Pop出去,然后再初始化新的状态,并调用新状态的Enter()函数。
void CGameStateManager::ChangeGameState(CGameState* state)
{
if (!m_ActiveStateStack.empty())
{
m_ActiveStateStack.back()->Exit();
m_ActiveStateStack.pop_back();
}
m_ActiveStateStack.push_back(state);
Init(state);
m_ActiveStateStack.back()->Enter();
}
GameState.h文件如下:
/********************************************************************
created: 2012/02/25
created: 25:2:2012 15:05
filename: E:\Ogre\Project\Ogre_Project\Game_Demo\GameState.h
file path: E:\Ogre\Project\Ogre_Project\Game_Demo
file base: GameState
file ext: h
author: Star
purpose: the parent of all the game state
*********************************************************************/
#ifndef _ZH_GAMESTATE_H_
#define _ZH_GAMESTATE_H_
#pragma once
#include "OgreFramework.h"
class CGameState;
class CGameStateListener
{
public:
CGameStateListener(){};
virtual ~CGameStateListener(){};
virtual void ManageGameState(Ogre::String stateName, CGameState* state) = 0;
virtual CGameState* FindByName(Ogre::String stateName) = 0;
virtual void ChangeGameState(CGameState* state) = 0;
virtual bool PushGameState(CGameState* state) = 0;
virtual void PopGameState() = 0;
virtual void PauseCurGameState() = 0;
virtual void Shutdown() = 0;
virtual void PopAllAndPushGameState(CGameState* state) = 0;
};
class CGameState : public OIS::KeyListener,public OIS::MouseListener, public OgreBites::SdkTrayListener
{
public:
static void Create(CGameStateListener* parent, const Ogre::String name) {};
void destroy() { delete this; }
virtual void Enter() = 0;
virtual void Exit() = 0;
virtual bool Pause() { return true;}
virtual void Resume() {};
virtual void Update(double timeSinceLastFrame) = 0;
protected:
CGameState(){};
CGameState* FindByName(Ogre::String stateName) {return m_pListener->FindByName(stateName); }
void ChangeGameState(CGameState* state) { m_pListener->ChangeGameState(state); }
bool PushGameState(CGameState* state) { return m_pListener->PushGameState(state);}
void PopGameState() {m_pListener->PopGameState(); }
void Shutdown() { m_pListener->Shutdown(); }
void PopAllAndPushGameState(CGameState* state) { m_pListener->PopAllAndPushGameState(state); }
CGameStateListener* m_pListener;
Ogre::Camera* m_pCamera;
Ogre::SceneManager* m_pSceneMgr;
Ogre::FrameEvent m_FrameEvent;
};
// macro define
#define DECLARE_GAMESTATE_CLASS(T) \
static void Create(CGameStateListener* pListener, const Ogre::String name) \
{ \
T* newState = new T(); \
newState->m_pListener = pListener; \
pListener->ManageGameState(name, newState); \
} \
#endif //_ZH_GAMESTATE_H_
上面代码中最后有一个宏定义,它的做用就是定义每个状态,并把它自己注册进状态管理类里。这是为了让你少写一些代码,不然你每定义一个状态类就得写一遍这个函数,有这个宏就方便多了。
可以看一下类图:
GameStateManager.h
/********************************************************************
created: 2012/02/25
created: 25:2:2012 16:05
filename: E:\Ogre\Project\Ogre_Project\Game_Demo\GameStateManager.h
file path: E:\Ogre\Project\Ogre_Project\Game_Demo
file base: GameStateManager
file ext: h
author: Star
purpose: Manage all the game states
*********************************************************************/
#ifndef _ZH_GAMESTATEMANAGER_H_
#define _ZH_GAMESTATEMANAGER_H_
#pragma once
#include "GameState.h"
class CGameStateManager : public CGameStateListener
{
public:
typedef struct
{
Ogre::String name;
CGameState* state;
} SState_info;
CGameStateManager();
~CGameStateManager();
void ManageGameState(Ogre::String stateName, CGameState* state);
CGameState* FindByName(Ogre::String stateName);
void Start(CGameState* state);
void ChangeGameState(CGameState* state);
bool PushGameState(CGameState* state);
void PopGameState();
void PauseCurGameState();
void Shutdown();
void PopAllAndPushGameState(CGameState* state);
protected:
void Init(CGameState* state);
std::vector<CGameState*> m_ActiveStateStack;
std::vector<SState_info> m_States;
bool m_bShutdown;
};
#endif //_ZH_GAMESTATEMANAGER_H_
这个就是状态管理类,这里边的Start函数,就是程序消息循环的位置了。状态的切换也都在这里边发生。
这些函数的实现我就不复制,粘贴了,到时最后会发一个代码链接。
有了这些状态定义,我们现在就来试一下,先做一个MenuState。
MenuState.h
/********************************************************************
created: 2012/02/27
created: 27:2:2012 11:40
filename: E:\Ogre\Project\Ogre_Project\Game_Demo\MenuState.h
file path: E:\Ogre\Project\Ogre_Project\Game_Demo
file base: MenuState
file ext: h
author: Star
purpose: the menu of the game
*********************************************************************/
#ifndef _ZH_MENUSTATE_H_
#define _ZH_MENUSTATE_H_
#pragma once
#include "GameState.h"
class CMenuState : public CGameState
{
public:
CMenuState();
DECLARE_GAMESTATE_CLASS(CMenuState)
void Enter();
void CreateScene();
void Exit();
bool keyPressed(const OIS::KeyEvent &evt);
bool keyReleased(const OIS::KeyEvent &evt);
bool mouseMoved( const OIS::MouseEvent &evt);
bool mousePressed( const OIS::MouseEvent &evt, OIS::MouseButtonID id );
bool mouseReleased( const OIS::MouseEvent &evt, OIS::MouseButtonID id );
void buttonHit(OgreBites::Button* button);
void Update(double timeSinceLastFrame);
private:
bool m_bQuit;
};
#endif //_ZH_MENUSTATE_H_
主要讲一下Enter函数,这里边就要创建一下菜单界面了,还有Camera和SceneManager的创建,用的是OGRE现在带的SDKTray。然后也可以创建场景,这样就会有比较动感的开始菜单界面了等。
void CMenuState::Enter()
{
Ogre::FontManager::getSingleton().getByName("SdkTrays/Caption")->load();
COgreFramework::getSingletonPtr()->m_pLog->logMessage("Entering MenuState.h.");
m_pSceneMgr = COgreFramework::getSingletonPtr()->m_pRoot->createSceneManager(ST_GENERIC, "MenuSceneMgr");
m_pSceneMgr->setAmbientLight(Ogre::ColourValue(0.7f, 0.7f, 0.7f));
m_pCamera = m_pSceneMgr->createCamera("MenuCam");
m_pCamera->setPosition(Vector3(0, 25, -50));
m_pCamera->lookAt(Vector3(0, 0, 0));
m_pCamera->setNearClipDistance(1);
m_pCamera->setAspectRatio(Real(COgreFramework::getSingletonPtr()->m_pViewport->getActualWidth())/
Real(COgreFramework::getSingletonPtr()->m_pViewport->getActualHeight()));
COgreFramework::getSingletonPtr()->m_pViewport->setCamera(m_pCamera);
COgreFramework::getSingletonPtr()->m_pTrayMgr->destroyAllWidgets();
COgreFramework::getSingletonPtr()->m_pTrayMgr->showFrameStats(OgreBites::TL_BOTTOMLEFT);
COgreFramework::getSingletonPtr()->m_pTrayMgr->showLogo(OgreBites::TL_BOTTOMRIGHT);
COgreFramework::getSingletonPtr()->m_pTrayMgr->showCursor();
COgreFramework::getSingletonPtr()->m_pTrayMgr->createButton(OgreBites::TL_CENTER, "EnterBtn", "Enter GameState", 250);
COgreFramework::getSingletonPtr()->m_pTrayMgr->createButton(OgreBites::TL_CENTER, "ExitBtn", "Exit OgreFramework", 250);
COgreFramework::getSingletonPtr()->m_pTrayMgr->createLabel(OgreBites::TL_TOP, "MenuLbl","Menu mode", 250);
CreateScene();
}
这边有一个注意项:
如果没有这句的话
Ogre::FontManager::getSingleton().getByName("SdkTrays/Caption")->load();
我这边顶上的那个Label里是不显示文字的,在他的论坛里找到这么一个解决方案。
最后的效果截图:
通过这样的框架就可以搭建自己的游戏了,只要继承GameState,实现自己的场景绘制和UI,就好了。期待你的游戏的产生。
资源下载:
http://download.csdn.net/detail/sunstar1989/4094433
用OGRE1.74搭建游戏框架(三)--加入人物控制和场景
有了前面的状态机,我们就可以很方便的扩展出自己的游戏场景状态的类。
现在我们要写一个RunState的类,这个就是游戏的主场景所在的地方,从MenuState可以跳转过来。
一、加入第三人称控制器
对于人物的控制,新版的OGRE已经给出了一个示例,就是那个Sample_Charater。
里边是对一个OGRE新模型的控制,操作起来感觉不错,就是向前跑动的时候没法看身后的东西,得停下来,Camera才会回到自由模式。
这个地方主要用到两个类:
SdkCameraMan:封装了OGRE的像机,可以比较自由的控制像机。
SinbadCharacterController:对于Sinbad的角色的控制以及动画的播放。
只要声明并定义好这两个类就能快速的实现第三人称的控制。
CRunState.h
class CRunState : public CGameState
{
public:
CRunState();
DECLARE_GAMESTATE_CLASS(CRunState)
void Enter();
void CreateScene();
void Exit();
bool Pause();
void Resume();
bool keyPressed(const OIS::KeyEvent &evt);
bool keyReleased(const OIS::KeyEvent &evt);
bool mouseMoved( const OIS::MouseEvent &evt);
bool mousePressed( const OIS::MouseEvent &evt, OIS::MouseButtonID id );
bool mouseReleased( const OIS::MouseEvent &evt, OIS::MouseButtonID id );
void Update(double timeSinceLastFrame);
protected:
OgreBites::SdkCameraMan* m_pCameraMan;
bool m_bIsQuit;
SinbadCharacterController* m_pChara;
};
在Enter函数中初始化:
void CRunState::Enter()
{
COgreFramework::getSingletonPtr()->m_pLog->logMessage("Entering RunState.h.");
m_pSceneMgr = COgreFramework::getSingletonPtr()->m_pRoot->createSceneManager(ST_GENERIC, "RunSceneMgr");
m_pSceneMgr->setAmbientLight(Ogre::ColourValue(0.3f, 0.3f, 0.3f));
m_pCamera = m_pSceneMgr->createCamera("MainCam");
m_pCamera->setNearClipDistance(5);
m_pCamera->setAspectRatio(Real(COgreFramework::getSingletonPtr()->m_pViewport->getActualWidth())/
Real(COgreFramework::getSingletonPtr()->m_pViewport->getActualHeight()));
COgreFramework::getSingletonPtr()->m_pViewport->setCamera(m_pCamera);
m_pCameraMan = new OgreBites::SdkCameraMan(m_pCamera);
m_pCameraMan->setStyle(OgreBites::CS_MANUAL);
m_pChara = new SinbadCharacterController(m_pCamera);
COgreFramework::getSingletonPtr()->m_pTrayMgr->destroyAllWidgets();
COgreFramework::getSingletonPtr()->m_pTrayMgr->hideCursor();
CreateScene();
}
在那些键盘和鼠标消息中响应人物和摄像机的事件。最后要在Update中更新动画。
void CRunState::Update(double timeSinceLastFrame)
{
if (m_bIsQuit)
{
Shutdown();
return;
}
if (m_pChara)
m_pChara->addTime(timeSinceLastFrame/1000); // from miliseconds to seconds
}
这边注意,因为我们前面用的是毫秒,这边动画更新需要的是秒为单位,所以要除,否则画面不会动。
二、载入.Scene场景文件
如果要用代码加入物体,不仅麻烦(得算坐标),而且不易修改(修改后得重新编译)。于是就有了场景文件的出现,而且也有了开源的场景编辑器(Ogitor)。
Ogitor是一个基于QT的OGRE场景的编辑器,可以到官网上下载:
http://www.ogitor.org/HomePage
在我们的工程中要用到的就是三个文件(在Ogitor的安装目录的/SampleApp_Source/下):
DotSceneLoader.h
DotSceneLoader.cpp
rapidxml.h
把他们拷进我们的工程里,我们将要使用他们来载入场景。
这时,会发现程序有错,DotSceneLoader.cpp里要引用到PagedGeometry的一些东西,这是一个用来植树造林的工具,我们也可以去下载。
http://www.ogre3d.org/tikiwiki/PagedGeometry+Engine
官方没有提供已经编译好的,所以需要自己编译,要配置OGRE的头文件和库的路径。
新的OGRE中的material的一个方法改了,所以编译会有错误:
需要将:
bestTechnique = material->getBestTechnique(material->getLodIndexSquaredDepth(parent->minDistanceSquared));
转换为:
bestTechnique = getLodIndex(parent->minDistanceSquared)
这样就可以编译出LIB库了。
回到我们的工程:
在CreateScene函数中载入场景:
void CRunState::CreateScene()
{
DotSceneLoader* pDotSceneLoader = new DotSceneLoader();
pDotSceneLoader->parseDotScene("CubeScene.xml", "General", m_pSceneMgr, m_pSceneMgr->getRootSceneNode());
delete pDotSceneLoader;
// add a bright light above the scene
Light* light = m_pSceneMgr->createLight();
light->setType(Light::LT_POINT);
light->setPosition(-10, 40, 20);
light->setSpecularColour(ColourValue::White);
// create a floor mesh resource
MeshManager::getSingleton().createPlane("floor", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
Plane(Vector3::UNIT_Y, 0), 100, 100, 10, 10, true, 1, 10, 10, Vector3::UNIT_Z);
// create a floor entity, give it a material, and place it at the origin
Entity* floor = m_pSceneMgr->createEntity("Floor", "floor");
floor->setMaterialName("Examples/Rockwall");
floor->setCastShadows(false);
m_pSceneMgr->getRootSceneNode()->attachObject(floor);
}
这边要注意在资源文件(resource.cfg)中加入CubeScene.xml和Cube模型的资源路径,否则OGRE不会载入他们,就找不到这些资源。
最后就可以看到结果了:
尝试着载入了Ogitor自带的Sample文件,SampleScene3.scene。注意要把资源路径配好。
人缩小一倍还比房子大。。。有点像绿巨人,人物的位置要根据地形高低来设置好。